XSS to Account Takeover: Cookies, localStorage, CSP Bypasses
Cybersecurity
Modern apps store sessions in cookies, tokens in localStorage, and trust a Content Security Policy to stop XSS from being catastrophic. Here is how to demonstrate full account takeover from an XSS, and how to bypass the CSPs that look strict on paper.
By Arjun Raghavan, Security & Systems Lead, BIPI · June 14, 2023 · 10 min read
XSS without impact is informational
alert(1) is not a finding anymore. You need to show what an attacker actually does with the execution. That means session theft, action on behalf, or data exfiltration.
The three storage targets
- HttpOnly cookies, unreadable from JavaScript, but usable via fetch with credentials
- Non-HttpOnly cookies, readable directly via document.cookie
- localStorage and sessionStorage, fully readable, where most SPA tokens live
ATO via HttpOnly cookies
If the session cookie is HttpOnly, you cannot read it, but you can use it. Fetch sensitive endpoints with credentials, exfiltrate the response. Better, perform actions, change the email and password, then log in as the victim.
The PoC is a small script that calls /api/account/email with a PUT, then /api/account/password with a PUT, then exits. Two fetches, one new password, full takeover.
ATO via localStorage
JWTs in localStorage are the simplest path. Read the token, exfiltrate it, replay it from your machine. Most SPAs send the token as a Bearer header, so it works from anywhere on the internet.
CSP bypass patterns
A real CSP forces you to find a workaround. Here are the patterns that keep working in the wild.
- unsafe-inline still present, game over
- Nonce reuse across requests, steal a nonce, inject inline
- Trusted CDN with a JSONP endpoint or AngularJS, script-src bypass via gadget
- Allowlisted self with file upload that serves text/javascript
- Loose object-src or base-uri, inject a base tag, redirect script loading
- strict-dynamic with a sink that calls eval or setTimeout with a string
Demonstrating the bypass
Show the CSP header in your PoC. Then show the payload running despite it. If you used a gadget, name it and link the documented bypass. Triagers know the gadget list and respect a clean demonstration.
Exfiltration channels under CSP
- fetch to an allowlisted endpoint that mirrors data
- navigator.sendBeacon if connect-src allows it
- DNS prefetch via link rel for short payloads
- Form submission to an allowlisted POST endpoint
Self-XSS plus clickjacking
A self-only XSS in the account settings page is informational, until you find that the page is framable. Combine with clickjacking to deliver the payload, suddenly it is one-click ATO. Always check X-Frame-Options and frame-ancestors.
Reporting structure
- Title, stored XSS in profile bio leads to one-click account takeover
- PoC, payload plus a five-line script that changes victim email and password
- Impact, every user, persistent, no interaction beyond viewing a profile
- Remediation, sanitise on output, tighten CSP, set SameSite on session cookie
alert(1) earns nothing. A two-fetch ATO PoC earns a critical. Same bug, different demonstration.
Read more field notes, explore our services, or get in touch at info@bipi.in. Privacy Policy · Terms.