Glossary/Detection Engineering/Cross Site Scripting (XSS)

What Is Cross-Site Scripting (XSS)? Types and Defenses

Cross-site scripting (XSS) is an injection attack in which a web application delivers attacker-supplied script to another user's browser, where it runs with the privileges of the trusted site.

A user opens a product page on a site they have used for years, logged in, trusted. The page renders normally. What they do not see is a line of attacker-controlled JavaScript that a previous visitor planted in a review field, now running inside their browser with the full authority of that trusted site. It reads their session cookie and sends it to a server the attacker controls. Seconds later the attacker is logged in as that user, no password required. Nothing on the page looked wrong, because the malicious code arrived from the same origin as everything else.

That is cross-site scripting. The browser cannot tell the difference between the script the site intended to send and the script an attacker smuggled into it, so it runs both with the same trust. This guide covers what XSS is and why the browser falls for it, the three types a defender actually encounters (stored, reflected, and DOM-based), what the payloads do once they run, where XSS sits in the OWASP Top 10, the signals that expose it, and the defenses that stop it. It is written for the people who have to find and stop it: SOC analysts triaging web alerts, threat hunters reading WAF and application logs, and DFIR responders reconstructing how a session got stolen.

What is cross-site scripting?

Cross-site scripting is an injection attack in which an attacker gets a web application to deliver malicious script, usually JavaScript, to another user's browser, where it executes with the privileges of the vulnerable site. OWASP defines it plainly: XSS attacks are a type of injection in which malicious scripts are injected into otherwise benign and trusted websites.

The root cause is a failure to separate data from code. A web application takes input from one user (a comment, a search term, a profile field, a URL parameter) and later includes that input in a page sent to another user. If the application does not properly neutralize the input before placing it in the HTML, an attacker can supply input that the browser interprets as script rather than as text. The browser then executes it as if the site had written it.

The reason this matters is the same-origin trust model the whole web rests on. A browser grants a page's scripts access to that origin's cookies, local storage, and DOM, and it trusts that those scripts came from the site. XSS breaks that assumption. The injected script runs from the victim's own session, inside the trusted origin, so it inherits every permission the legitimate code has. It can read the session token, act as the user, rewrite the page, or harvest whatever the user types next. The attacker never touches the server's filesystem; they borrow the user's browser and the site's trust.

The three types of cross-site scripting

Cross-Site Scripting · CWE-79
Three types, one split that decides detection
Stored and reflected XSS reflect off the server and show up in request and response logs. DOM-based runs in the browser and can leave no server-side trace.
TYPE 01
Stored
Payload saved on the server (DB, comment, profile) and served to every viewer. Inject once, fire for all.
SERVER · MASS
TYPE 02
Reflected
Payload in the request, bounced straight back in the response. Delivered per victim by a crafted link.
SERVER · ONE-TO-ONE
TYPE 03
DOM-based
Page script writes attacker input (after #) into a sink like innerHTML. May never reach the server.
CLIENT · BLIND SPOT
Defender takeaway Catch stored and reflected XSS in request and response logs: script-like input in fields, input reflected verbatim in responses. DOM-based leaves no server trace, so lean on client-side review and CSP violation reports. Context-aware output encoding fixes all three; HttpOnly cookies take the session token off the table.

XSS is classified by where the malicious input lives and how it reaches the victim. The three types differ in their delivery, their reach, and how you hunt for them.

Stored XSS is the most dangerous. The malicious script is saved on the server, in a database, a comment, a forum post, a product review, a user profile, and then served to every user who views that content. The attacker injects once and the payload fires for everyone who loads the page, with no further action from the attacker. A stored XSS in a high-traffic field is effectively a worm vector: it can hit thousands of authenticated users from a single injection, which is why it is the variant that produces mass account compromise.

Reflected XSS is non-persistent. The malicious script is part of the request, typically in a URL parameter, and the server reflects it straight back in the response: an error message, a search-results page, anything that echoes input. Because the payload is not stored, the attacker has to deliver the crafted link to each victim, usually through phishing or a malicious link on another site. The victim clicks, the request carries the script, the response reflects it, and it runs in that one victim's browser. One request, one response, one target at a time.

DOM-based XSS never involves the server reflecting the payload at all. It lives entirely in the client-side JavaScript. The page's own script reads attacker-controllable input (the URL fragment after #, document.location, window.name) and writes it into the page through a dangerous sink like innerHTML or document.write without sanitizing it. The malicious data may never reach the server, which is exactly why server-side logging and many WAFs miss it. DOM-based XSS was identified by Amit Klein in 2005 and has only grown more relevant as applications push more logic into the browser.

The line that matters operationally: stored and reflected XSS are server-side reflection problems you can often see in request and response logs, while DOM-based XSS is a client-side problem that may leave no server-side trace at all.

XSS types compared

The three types share the same root cause, untrusted input rendered as code, but they differ along the axes a defender cares about: where the payload lives, how the victim is reached, blast radius, and where you look for it.

TypeWhere the payload livesHow the victim is reachedBlast radiusServer-side log trace
StoredPersisted on the server (DB, comment, profile)Any user who views the contentMass; every viewerYes, in the stored content and the serving response
ReflectedIn the request (URL parameter)A crafted link, usually via phishingOne victim per delivered linkYes, in the request and the reflecting response
DOM-basedIn client-side JavaScript, often after #A crafted link processed by page scriptOne victim per delivered linkOften none; payload may never hit the server

Two patterns matter more than the rows. First, stored XSS is the one that scales: a single injection reaches every viewer, so it is the variant most likely to produce a breach-level event rather than a single hijacked session. Second, DOM-based XSS is the blind spot: because the payload can stay client-side, the request that carries it may look benign to the server and to a network sensor, so detection has to include client-side review, not just log analysis.

What an XSS payload actually does

The injected <script> is just a delivery mechanism. What makes XSS dangerous is what runs inside it, executing with the victim's session and the site's origin.

Session and cookie theft. The classic payload reads document.cookie and exfiltrates the session token to an attacker server. With a valid session cookie that is not flagged HttpOnly, the attacker replays it and is logged in as the victim with no password and no second factor prompt. This kind of cookie logging is the most common goal of a real-world XSS, and it is why session hijacking and XSS are so tightly linked.

Credential and data harvesting. The script can rewrite the page to inject a fake login prompt or a payment form that posts to the attacker, turning a trusted page into a credential harvesting tool. Because the form appears on the legitimate origin with the real URL in the address bar, it defeats the usual phishing tells. The script can also silently read anything already on the page: account details, messages, tokens embedded in the DOM.

Acting as the user. Running inside the session, the script can make authenticated requests the user never intended: change the account email, add an attacker's device, transfer funds, post content, escalate privileges. It performs these actions from the user's own browser, so they carry the user's identity and pass server-side authorization checks.

Keylogging and page manipulation. The payload can attach listeners that capture every keystroke, redirect the user to a malicious site, or deface the page. In a stored-XSS worm, the payload also re-injects itself into content the victim posts, propagating to the next viewer.

The throughline: once the script runs, the attacker has whatever the user has. XSS is not a defacement nuisance; it is full client-side compromise of the victim's relationship with the site.

XSS in the OWASP Top 10

XSS has been a fixture of the OWASP Top 10 for as long as the list has existed, but its place changed in the current edition. In the OWASP Top 10 2017, cross-site scripting was its own category, A7:2017-Cross-Site Scripting. In the OWASP Top 10 2021, XSS was merged into A03:2021-Injection, reflecting that XSS is fundamentally an injection flaw alongside SQL injection and the rest. The Injection category, which now includes XSS, ranks third on the 2021 list, and OWASP reports that the combined category's mapped weaknesses had over 270,000 occurrences in the analyzed data, among the most of any category.

The reclassification is not cosmetic. It tells a defender how to think about XSS: it is the same data-versus-code failure as every other injection, just resolved in the browser rather than the database or the shell. The fix follows the same logic too, treat all input as untrusted and neutralize it before it is interpreted, which is why the defenses below mirror the defenses against injection generally.

The relevant identifiers for tracking and detection are the CWE entries: CWE-79 (Improper Neutralization of Input During Web Page Generation) is the canonical XSS weakness, and it sits under the broader injection family. Using CWE-79 in tickets and detections keeps everyone describing the same flaw.

Detection signals in your telemetry

XSS hides in normal web traffic, which is what makes it harder to spot than a noisy brute force. The signals are there, but they require looking at request content and, for the DOM-based case, at the client.

Script-like payloads in input fields and parameters. The most direct signal is request data containing markup that should be plain text: <script>, onerror=, javascript:, <img src=x onerror=...>, encoded variants like %3Cscript%3E, or event-handler attributes in a field meant for a name or a search term. A web application firewall and request logging surface these, though attackers obfuscate heavily to evade signatures.

Outbound requests to unexpected domains. A session-theft payload has to exfiltrate the token somewhere. A burst of requests carrying cookie-shaped data to a domain registered minutes ago, or any unexplained beacon from a page that should not call out, is a strong post-exploitation signal. This is often the first thing visible when the injection itself was missed.

Reflected input in responses. When request input appears verbatim in the HTML response without encoding, that is the reflected-XSS condition itself. Comparing input parameters against response bodies, in testing or in monitoring, catches the reflection before an attacker weaponizes it.

Anomalies in stored content. For stored XSS, the payload sits in your own data. Review of user-generated content for embedded markup and scripts, especially in fields that should be plain text, finds the injection that will otherwise fire on every viewer.

Client-side blind spots. DOM-based XSS may produce no server-side trace, so detection extends to the browser: Content-Security-Policy violation reports, which fire when a page tries to run an inline or unauthorized script, are one of the few signals that catch a client-side payload the server never saw. CSP report endpoints are worth wiring up for exactly this reason.

Defenses that actually stop XSS

XSS is solved by treating all input as untrusted and never letting it be interpreted as code. The defenses layer so that the application stops the common cases and the browser backstops the rest. In rough order of impact:

Context-aware output encoding. The single most important control. Whenever user-controllable data is placed into a page, encode it for the exact context it lands in: HTML body, HTML attribute, JavaScript, URL, or CSS each need different encoding. Encoding turns <script> into inert text the browser renders rather than runs. Most modern frameworks (React, Angular) encode by default, which is why the dangerous cases are the places that bypass that default, such as dangerouslySetInnerHTML or direct innerHTML writes.

Input validation and sanitization. Validate input against an allowlist of what is expected (length, type, format) and reject the rest. For input that must contain HTML, such as a rich-text comment, sanitize it with a vetted library like DOMPurify rather than a hand-rolled filter, because attackers have decades of bypasses for naive blocklists. Validation complements encoding; it does not replace it.

Content Security Policy. A strong CSP is the browser-level backstop. By restricting which script sources can execute and blocking inline scripts, a well-configured CSP can neutralize an injected payload even when an XSS flaw slips through, and its violation reports double as a detection signal. CSP is defense in depth, not a primary fix, a flaw is still a flaw, but it sharply limits what a successful injection can do.

HttpOnly and Secure cookies. Mark session cookies HttpOnly so client-side JavaScript cannot read them, which directly defeats the document.cookie theft that is XSS's most common payload, and Secure so they only travel over HTTPS. This does not fix the XSS, but it removes the highest-value prize from the attacker's reach.

Safe DOM APIs and framework defaults. For DOM-based XSS, the fix is in the client code: avoid dangerous sinks like innerHTML, document.write, and eval with untrusted data, prefer textContent and safe framework bindings, and adopt Trusted Types where supported to enforce it. Since DOM-based XSS never touches the server, this is the only place it can be fixed.

These controls map to the broader discipline of application security: build the encoding and validation in during development, test for the flaw before release, and add CSP and cookie flags as runtime backstops.

Frequently Asked Questions

What is cross-site scripting in simple terms?

Cross-site scripting is a web attack where an attacker tricks a trusted website into delivering malicious JavaScript to another user's browser. The browser runs that script as if the site wrote it, so the attacker can steal the user's session, read their data, or act as them. It exploits the website's failure to separate user input from executable code, not a flaw in the user's machine.

What are the three types of XSS?

The three types are stored XSS, where the payload is saved on the server and served to every viewer; reflected XSS, where the payload is in a request and bounced back in the response to one victim per crafted link; and DOM-based XSS, where the page's own JavaScript writes attacker-controlled input into the page without ever sending it to the server. Stored is the most dangerous because it hits every viewer; DOM-based is the hardest to detect because it can leave no server-side trace.

What is the difference between stored and reflected XSS?

Stored XSS persists on the server (in a database, comment, or profile) and fires automatically for every user who views the affected content, so one injection can compromise many users. Reflected XSS is not stored; the payload travels in each request, usually a crafted link sent by phishing, and only affects the single user who clicks it. Stored XSS scales to a mass event; reflected XSS targets one victim at a time.

Is XSS part of the OWASP Top 10?

Yes. In the OWASP Top 10 2017, cross-site scripting was its own category (A7). In the OWASP Top 10 2021 it was merged into A03:2021-Injection, because XSS is fundamentally an injection flaw resolved in the browser. The Injection category ranks third on the 2021 list, and the canonical weakness identifier for XSS is CWE-79.

How do you prevent XSS attacks?

The primary defense is context-aware output encoding: neutralize all user-controllable data before it is placed in a page so the browser renders it as text, not code. Layer that with input validation and sanitization using a vetted library, a strong Content Security Policy as a browser-level backstop, and HttpOnly cookies so an injected script cannot steal the session token. For DOM-based XSS, avoid dangerous sinks like innerHTML and eval in client-side code.

What can an attacker do with XSS?

Running inside the victim's session, an XSS payload can steal session cookies and hijack the account, inject fake login or payment forms to harvest credentials, make authenticated requests as the user (changing settings, transferring funds), log keystrokes, redirect the browser, or deface the page. With stored XSS the payload can also self-propagate to every other viewer of the affected content.

The bottom line

Cross-site scripting is an injection flaw that gets a trusted site to deliver attacker JavaScript to another user's browser, where it runs with the victim's session and the site's origin. The three types split along where the payload lives: stored XSS persists on the server and hits every viewer, reflected XSS bounces a crafted link back to one victim, and DOM-based XSS runs entirely in client-side JavaScript and may leave no server-side trace. OWASP folded XSS into the Injection category in its 2021 Top 10, which is the right mental model, the same data-versus-code failure as every other injection, resolved in the browser.

For a defender, the payload is the point. XSS is not page defacement; it is session theft, credential harvesting, and acting as the user, which is why a single stored injection can become a breach. The detection signals are script-like input in request data, reflected input in responses, anomalous outbound beacons carrying cookies, and CSP violation reports for the client-side case. The fixes are context-aware output encoding first, then input sanitization, a strong Content Security Policy, and HttpOnly cookies to take the session token off the table. Encode the output, validate the input, and assume the browser will run anything you let through.

Frequently asked questions

What is cross-site scripting in simple terms?

<p>Cross-site scripting is a web attack where an attacker tricks a trusted website into delivering malicious JavaScript to another user's browser. The browser runs that script as if the site wrote it, so the attacker can steal the user's session, read their data, or act as them. It exploits the website's failure to separate user input from executable code, not a flaw in the user's machine.</p>

What are the three types of XSS?

<p>The three types are stored XSS, where the payload is saved on the server and served to every viewer; reflected XSS, where the payload is in a request and bounced back in the response to one victim per crafted link; and DOM-based XSS, where the page's own JavaScript writes attacker-controlled input into the page without ever sending it to the server. Stored is the most dangerous because it hits every viewer; DOM-based is the hardest to detect because it can leave no server-side trace.</p>

What is the difference between stored and reflected XSS?

<p>Stored XSS persists on the server (in a database, comment, or profile) and fires automatically for every user who views the affected content, so one injection can compromise many users. Reflected XSS is not stored; the payload travels in each request, usually a crafted link sent by phishing, and only affects the single user who clicks it. Stored XSS scales to a mass event; reflected XSS targets one victim at a time.</p>

Is XSS part of the OWASP Top 10?

<p>Yes. In the OWASP Top 10 2017, cross-site scripting was its own category (A7). In the OWASP Top 10 2021 it was merged into A03:2021-Injection, because XSS is fundamentally an injection flaw resolved in the browser. The Injection category ranks third on the 2021 list, and the canonical weakness identifier for XSS is CWE-79.</p>

How do you prevent XSS attacks?

<p>The primary defense is context-aware output encoding: neutralize all user-controllable data before it is placed in a page so the browser renders it as text, not code. Layer that with input validation and sanitization using a vetted library, a strong Content Security Policy as a browser-level backstop, and <code>HttpOnly</code> cookies so an injected script cannot steal the session token. For DOM-based XSS, avoid dangerous sinks like <code>innerHTML</code> and <code>eval</code> in client-side code.</p>

What can an attacker do with XSS?

<p>Running inside the victim's session, an XSS payload can steal session cookies and hijack the account, inject fake login or payment forms to harvest credentials, make authenticated requests as the user (changing settings, transferring funds), log keystrokes, redirect the browser, or deface the page. With stored XSS the payload can also self-propagate to every other viewer of the affected content.</p>

Practice track
SOC Analyst Tier 1
Build your foundational skills to monitor, detect, and escalate security alerts. This track includes essential tools, basic log analysis, and introductory incident response labs.
Browse SOC Analyst Tier 1 Labs โ†’