
Cross Site Scripting (XSS) is one of the oldest and still most common web vulnerabilities, consistently appearing across bug bounty programs and pentest reports because almost every app handles user input that ends up rendered in a browser. At its core, XSS means your script runs in someone else's browser, inside their origin and with their session, which is enough to steal cookies, perform actions as them, or rewrite the page.
This guide covers the three types of XSS with concrete vectors, how to test each one with Burp Suite, dalfox, and XSStrike, how they map to the OWASP Top 10 and WSTG, and the encoding and CSP controls that genuinely prevent it. XSS lives under A03:2021 Injection and WSTG-INPV-01/02 and WSTG-CLNT-01.
XSS is a vulnerability that lets an attacker inject client-side script into content served to other users, so the browser executes attacker code as if the site wrote it. Because the script runs in the victim's origin, it can read cookies and DOM-stored tokens, make authenticated requests, log keystrokes, or fully rewrite the page for phishing.
The root cause is always the same: untrusted input reaches an output context (HTML, an attribute, a JavaScript string, a URL) without the right encoding for that context. The differences between XSS types come down to where the payload travels before it executes, which is why understanding the types changes how you test. XSS is a fixture of the OWASP Top 10 under A03 Injection.
There are three primary types of XSS, separated by how the payload reaches the victim: reflected, stored, and DOM-based. A fourth label, blind XSS, is just stored XSS that fires in a context you can't see, like an admin log viewer.
location.hash flows into a sink like innerHTML without the server ever seeing the payload.Test by injecting context-breaking payloads into every input and observing whether they execute, then tailor the payload to the exact output context. Start with a unique marker string, find where it lands in the response, and craft a payload that breaks out of that context. XSS testing also fits inside the broader OWASP WSTG methodology.
# Classic probes by context
<script>alert(document.domain)</script> # raw HTML body
"><svg onload=alert(1)> # break out of an attribute
';alert(1);// # inside a JS string
javascript:alert(1) # href/src sink
# Automated fuzzing
dalfox url "https://target/search?q=FUZZ"
python3 xsstrike.py -u "https://target/search?q=test"For DOM XSS, skip the server and read the JavaScript: trace sources (location, document.referrer, postMessage) to sinks (innerHTML, eval, document.write). Burp Suite ties it together for proxying and manual replay, and dalfox or XSStrike accelerate the fuzzing. These belong in your web app test cases.
Stored XSS is generally the most severe type because the payload is served to every user who views the affected content, with no need to trick anyone into clicking a crafted link. A single injected comment or profile field can hit thousands of sessions automatically.
That changes both impact and exploitability. Reflected XSS requires social engineering to deliver the malicious URL and is often dampened by browser-side filters or short-lived links. Stored XSS spreads on its own and frequently lands in privileged contexts (an admin dashboard rendering user-submitted data), where stealing a session means account takeover of staff. In CVSS terms, stored XSS routinely scores higher because the attack complexity and user-interaction requirements drop.
Prevent XSS with context-aware output encoding as the primary control, then layer a Content-Security-Policy and safe DOM APIs on top. Encode every piece of untrusted data for the exact context it lands in: HTML-entity-encode for body text, attribute-encode inside attributes, and JavaScript-encode inside script. Modern frameworks like React and Angular do most of this automatically as long as you avoid escape hatches like dangerouslySetInnerHTML.
For DOM XSS, prefer textContent over innerHTML and never feed untrusted input to eval or document.write. Add a strict CSP (for example script-src 'self' with nonces, no unsafe-inline) so even an injected script is blocked from running, and consider Trusted Types to lock dangerous sinks. Encoding stops the bug; CSP limits the blast radius if one slips through.