# XSS through `__e2e_action_id` delivered by JSONP

Unescaped __e2e_action_id reflected into finishAction() JS, delivered cross-user via JSONP channel update by spoofing window_id on _m=edit.

- Payout: hidden
- Severity: low
- Program: Quora
- Found by: 0xnan
- Disclosed: 2018-03-08
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/259100
- Library page: https://bugbountylibrary.com/r/h1%3A259100
- Tags: area:web domain:web impact:ato kind:bounty platform:hackerone sev:low tech:rest technique:chained technique:source-review vuln:xss

**Target:** /webnode2/server_call_POST endpoint and tch.quora.com JSONP updates channel
**Root cause:** The __e2e_action_id POST parameter is reflected unescaped as the first argument of finishAction() in the JSONP channel update JS. The _m=edit method does not verify that the supplied window_id channel belongs to the caller's session, so the message is delivered to another user's channel.
**Steps:**
1. Log in and capture the `server_call_POST?_m=edit` request from a profile description update.
2. Set `window_id` and `_lm_window_id` to the victim channel name (e.g. `dep3501-3261853912009855464`).
3. Set `__e2e_action_id=',alert(1),'` in the same request body.
4. Send the request with attacker cookies/formkey/postkey; the edit applies to the attacker but the finishAction message is pushed to the victim channel.
5. Victim's `tch.quora.com/up/.../updates?callback=jsonp...` poll returns the injected JS, executing `alert(1)`.
**How found:** Noticed __e2e_action_id is generated as base36 and reflected into finishAction() JS unescaped, but not directly settable. Then observed _m=edit returns {value:null} and delivers updates via the user's channel, and that window_id is attacker-controlled without ownership validation (unlike _m=load_menu which 500s on mismatch).
**Exploitation:** Attacker reuses their own authenticated session and formkey/postkey but overrides window_id to the victim's channel. The server pushes the finishAction() message containing the injected __e2e_action_id to that channel; the victim's periodic JSONP update poll executes it with no interaction. Requires knowing the victim channel name (bruteforceable in part via check_livedeps).
**Escalation:** Chained a non-triggerable reflected XSS with a missing channel-ownership check on _m=edit to achieve zero-interaction delivery to another user.
**Insight:** When a reflected sink isn't directly reachable, look for a secondary server-side push channel (JSONP/long-poll) that echoes the same parameter, and test whether the channel identifier parameter is bound to the session.

