Overview
Cross-Site Scripting (XSS) is a web vulnerability where an application includes attacker-controlled data in a page in a way that a browser interprets as executable script rather than inert text. When that happens, the attacker's script runs in the victim's browser, in the context of the vulnerable site. XSS comes in three classic types — reflected, stored, and DOM-based — and this lesson introduces the concept and distinguishes those types. All examples assume an authorized, isolated practice target such as a CTF challenge or a deliberately vulnerable lab app.
Learning objectives
- Explain what XSS is at a high level and why it matters.
- Distinguish reflected, stored, and DOM-based XSS by where the untrusted data enters and is handled.
- Recognize that all three share the same root cause: untrusted data reaching a browser as script.
Core concept
XSS happens when an application takes data it does not fully control — a URL parameter, a form field, a stored comment, a value read from the page's own DOM — and places it into an HTML response (or into the live DOM) without neutralizing its meaning. If a browser then treats that data as markup or script instead of plain text, the attacker's code executes with the privileges of the page: it can read the DOM, access cookies not marked HttpOnly, make requests as the user, and alter what the victim sees.
The three types are not three different bugs so much as three delivery paths for the same underlying flaw:
- Reflected XSS — the malicious input travels in the request (typically a URL or form parameter) and is immediately reflected back in the server's response for that same request. The payload is not stored; it must be delivered to each victim, usually via a crafted link.
- Stored XSS — the malicious input is saved on the server (in a database, comment, profile field, log viewer, etc.) and later served to anyone who views that content. No special link is needed; the payload lies in wait.
- DOM-based XSS — the flaw is entirely in client-side JavaScript: script on the page reads attacker-influenced data (from the URL,
location.hash,document.referrer, storage, etc.) and writes it into a dangerous sink (likeinnerHTML) without safe handling. The server may never even see the payload.
How it works
For reflected XSS, the server echoes a request value into the response. If a search page prints You searched for: <value> and the value is placed into HTML unescaped, an attacker crafts a link whose parameter contains markup. When a victim opens that link, the server reflects the markup into the page and the browser runs it.
For stored XSS, the payload is persisted. An attacker submits it once — say, as a comment — and the server saves it. Every later visitor who loads the page receives the payload embedded in the HTML and their browser executes it. Because it self-delivers to all viewers, stored XSS is generally the most impactful of the three.
For DOM-based XSS, no server round-trip is required for the injection. Client JavaScript takes a source of untrusted data (for example, the fragment after # in the URL) and passes it to a sink that parses HTML or executes code. The transformation from data to script happens in the browser, so server-side output encoding alone will not catch it — the client code must handle the data safely.
Practical example
Consider an authorized lab search page whose server responds with:
<p>You searched for: SEARCH_TERM</p>
If SEARCH_TERM is inserted without encoding, a reflected payload delivered via a crafted link could turn the term into markup such as <script>...</script>, and the browser would execute it when the victim follows the link. On the same lab, a stored variant might involve a comment field: an attacker posts a comment containing markup, the app saves it verbatim, and the script runs for every visitor who reads the comments.
A DOM-based variant needs no server change at all. Suppose client JavaScript does:
document.getElementById("out").innerHTML = location.hash.slice(1);
Here location.hash is the source and innerHTML is the sink. Anything after # in the URL is written as HTML and parsed by the browser. The fix in each case is the same idea applied in the right place: treat the data as text, not markup — encode it for its output context on the server, and use safe DOM APIs (like textContent) on the client.
Cybersecurity relevance
XSS is one of the most common and consequential web vulnerabilities. A successful XSS lets an attacker run code as the victim within the trusted origin: hijacking sessions, performing actions on the user's behalf, stealing data rendered on the page, defacing content, or delivering further attacks. Classifying an instance as reflected, stored, or DOM-based tells a tester how the payload must be delivered and tells a defender where the fix belongs — in server-side rendering, in stored-data handling, or in client-side JavaScript. Across all three, the durable defense is context-aware output encoding / escaping: rendering untrusted data as data so the browser never mistakes it for code.
Common mistakes
- Thinking XSS is only
<script>tags. Event handler attributes,javascript:URLs, and injections into existing tags can all execute script; the type is defined by the delivery path, not the payload shape. - Assuming server-side escaping stops DOM-based XSS. DOM XSS lives in client JavaScript; if the browser builds the dangerous markup, server escaping never sees it.
- Confusing reflected and stored by payload rather than persistence. The distinction is whether the input is echoed for the current request only or saved and served to others.
Key takeaways
- XSS is untrusted data being interpreted by a browser as executable script within a trusted site's context.
- Reflected (echoed in the immediate response), stored (persisted and served to all viewers), and DOM-based (introduced by client-side JS via a source→sink flow) are three delivery paths for the same root cause.
- The classification guides delivery (for testing) and fix location (for defense); context-aware output encoding/escaping is the shared, primary defense.
Check your understanding
- How does stored XSS differ from reflected XSS in terms of persistence and how victims are reached?
- Why can server-side output encoding fail to prevent DOM-based XSS, and where must the fix be applied instead?

