# Medium Full Account Takeover By One Click

Stored XSS in Medium story headlines bypassed CSP via unsafe-inline, escalated to DOM XSS reading xsrfToken and PUT-changing victim email for full account takeover.

- Payout: $100
- Severity: high
- Program: Medium
- Credited: Abdullah Hussam
- Disclosed: 2016-06-23
- Write-up quality: 7/10
- Original: https://ahussam.me/Medium-full-account-takeover/
- Library page: https://bugbountylibrary.com/r/blog%3Aahussam-me-medium-full-account-takeover
- Tags: domain:web impact:ato kind:bounty platform:blog sev:high tech:rest technique:chained vuln:csrf vuln:xss

**Target:** Medium story headline rendering and /me/email endpoint
**Root cause:** Story headline was rendered without HTML filtering, and CSP used script-src 'unsafe-inline' without nonce, so injected inline scripts executed.
**Steps:**
1. Create story with headline `"><script>alert(1337)</script>` to confirm stored XSS.
2. CSP blocks external script loads; note script-src includes 'unsafe-inline' so inline scripts run.
3. Bypass headline length limit by injecting `"><script>document.write(decodeURIComponent(window.location.hash));</script>` and placing payload in URL fragment.
4. Fragment payload scans document.body.innerHTML for `xsrfToken`, extracts 16 chars after it.
5. Send `PUT https://medium.com/me/email` with `Content-Type: application/json` and `X-XSRF-Token: <token>`, body `{"email":"attacker@example.com"}`.
6. Victim clicks crafted link; email changes without password confirmation, enabling password reset takeover.
**How found:** Googled XSS vectors, landed on a Medium page that executed XSS, viewed source and noticed the headline was unfiltered; then read CSP and spotted 'unsafe-inline' without nonce, motivating escalation.
**Exploitation:** Attacker crafts a Medium story URL whose fragment contains JS; the stored XSS writes the fragment into the page, executes it same-origin, reads the xsrfToken from the DOM, and issues a same-origin PUT to /me/email to swap the victim's email, then triggers password reset.
**Escalation:** Chained limited stored XSS with DOM-based fragment injection to bypass headline length limits, then used same-origin XHR to defeat SOP and abuse missing password confirmation on email change for full ATO.
**Insight:** When CSP allows 'unsafe-inline' without nonce, inline stored XSS still executes; if input length is capped, move the payload into location.hash and use document.write(decodeURIComponent(...)) to smuggle a full script, then perform same-origin state-changing requests to bypass SOP.
