Reflected XSS
The application takes a value from the request - a URL parameter, form field, or header - and echoes it back into the immediate response without context-aware encoding. The payload lives in the request, so there is nothing stored: each victim has to be sent a crafted URL and click it. One request in, one response out, your code runs in their session.
# Unique canary per input - greppable, no special chars to get filteredhttp://<TARGET>/search?q=xss7a9c
# Find the canary in the response, read the surrounding markup, then pick the payload# body context → <svg onload=alert(document.domain)># attribute context → "><svg onload=alert(document.domain)># JS string context → ';alert(document.domain)//Success indicator: alert(document.domain) fires when you load the crafted URL. document.domain (not 1) proves which origin executed it - important when reflections bounce through redirects or iframes.
Victim model
Section titled “Victim model”Reflected XSS fires only for someone who sends your exact request, so impact depends entirely on delivery:
- The payload rides in a URL (GET) or a form you make the victim submit (POST). GET is the common case because a link is easy to deliver.
- Delivery is a social-engineering step - a link in an email, chat, or another site. That makes reflected XSS per-click and noisier to exploit than stored, which fires for every viewer with no link.
- It is still serious: one click from a logged-in admin is enough. The ceiling is the same as any XSS once code runs (see the cluster overview capability table).
Step 1 - Map and canary every input
Section titled “Step 1 - Map and canary every input”Send a unique, filter-neutral marker through each candidate and grep the response for it.
- Enumerate inputs: every URL parameter, every form field, and reflected headers (
Referer,User-Agent,X-Forwarded-For, sometimesCookie). - Send a distinct canary per input - e.g.
xss7a9c,xss7a9d- so you know which input produced which reflection. - Search the response body for each canary. No reflection means no reflected XSS on that input (it may still be stored or DOM-based).
- For every reflection, note where in the markup it landed - that is the context, and it decides the payload.
Plain alphanumeric canaries avoid the trap of a payload being filtered before you have even confirmed the input reflects.
Step 2 - Identify the context
Section titled “Step 2 - Identify the context”The canary’s surrounding markup tells you which payload class applies. This is the single most important step - a reflected payload that “doesn’t fire” is usually right input, wrong context, not a WAF.
| Canary lands in | Example | Payload class |
|---|---|---|
| HTML body | <p>Results for xss7a9c</p> |
Open a tag: <svg onload=…> |
| Tag attribute | <input value="xss7a9c"> |
Break out: "><svg onload=…> or add a handler: " onfocus=… autofocus x=" |
| JS string | var q = "xss7a9c"; |
Break the string: ";alert(document.domain)// |
| URL attribute | <a href="xss7a9c"> |
javascript:alert(document.domain) |
| CSS | <style>…xss7a9c…</style> |
CSS-injection; rarely direct JS in modern browsers |
See Payload construction for the full per-context catalog, and Filter bypasses when the obvious payload is stripped or encoded.
Step 3 - Check the reflection’s encoding
Section titled “Step 3 - Check the reflection’s encoding”The same input often reflects in more than one place, encoded differently in each. A value HTML-entity-encoded in the visible body may be reflected raw inside a hidden attribute or a JS variable. Test every reflection, not just the first:
# One input, three reflections - only the third is exploitable<p>Results for: <svg></p> ← entity-encoded, dead<input value="<svg>"> ← entity-encoded, dead<script>var q="</script>...";</script> ← raw in JS string, LIVEConfirm and escalate
Section titled “Confirm and escalate”Once a payload fires:
- Replace the
alertproof with the real action - usually session exfiltration. The cookie-logger pattern and callback infrastructure live in XSS to session. - If cookies are
HttpOnly, pivot to an in-browser CSRF action or scrape the DOM for data and tokens - see XSS+CSRF chain. - Build the final deliverable: the crafted URL (or auto-submitting form page) you would actually send a victim.
Common failure modes
Section titled “Common failure modes”- Reflection is HTML-entity-encoded everywhere. The app is output-encoding correctly in this sink. Check other sinks/contexts for the same input before concluding it is safe.
- Fires in Repeater but not the browser. The browser may re-encode the URL, or a client-side framework may re-render and strip it. Test in a real browser, and consider whether it is actually DOM-based.
- Payload works logged-out but the target is logged-in-only. Make sure your proof runs in the authenticated view; some reflections only exist on authenticated pages.
Quick reference
Section titled “Quick reference”| Step | Action |
|---|---|
| Map | Every param, field, and reflected header |
| Canary | Unique alphanumeric marker per input |
| Context | Read the markup around each reflection |
| Payload | Pick by context (catalog) |
| Bypass | If filtered, see Filter bypasses |
| Confirm | alert(document.domain) |
| Escalate | Session theft / CSRF chain |
Next move
Section titled “Next move”- Reflection in HTML body, no filtering →
<svg onload=…>; escalate straight to session theft - Reflection inside an attribute → break out with
">or addautofocus+onfocus; see Payload construction - Reflection inside a JS string →
';alert(document.domain)//, then repair the string so the page doesn’t break - Payload stripped or encoded → Filter bypasses
- No reflection anywhere → the sink may be client-side (DOM-based) or persisted and rendered elsewhere (Stored / Blind)
- Confirmed but cookies are
HttpOnly→ XSS+CSRF chain or DOM scraping