Key Takeaways
- Scanners find the easy reflected XSS β the bugs that pay real money (stored XSS, DOM XSS, mXSS) require manual hunting
- Context is everything: the same payload that works in raw HTML fails inside a JavaScript string or tag attribute
- DOM-based XSS lives entirely in client-side code β you find it by reading JavaScript, not by fuzzing parameters
- WAF bypass is a skill, not a trick: understand what the filter blocks, then find what it allows
- Impact determines payout β escalate from alert(1) to account takeover or data exfiltration before reporting
Why XSS Still Pays in 2026
Cross-site scripting has been on the OWASP Top 10 for over two decades, and it still accounts for roughly 20% of all bug bounty payouts. The reason is simple: every web application reflects user input somewhere, and the attack surface keeps growing as applications move logic to the client side.
But the easy XSS β the reflected parameter that pops an alert box β gets caught by scanners and fixed before you ever see it. The XSS that pays in 2026 is the kind scanners cannot find: DOM-based injection through client-side JavaScript, stored XSS through multi-step workflows, and mutation XSS that exploits browser parsing quirks to bypass sanitizers.
This guide covers how to find those bugs. If you're looking for what automated scanners check, read our reflected XSS scanner detection guide first. This article picks up where scanners stop.
The Three Types of XSS (And Which Ones Pay)
Reflected XSS
The input goes in through a request parameter and comes back in the immediate response. The victim must click a crafted link. Reflected XSS on a login page or sensitive action page pays Medium. On a static marketing page, most programs mark it Low or Informational.
Scanners catch most reflected XSS. Your edge as a manual hunter is finding reflections in unusual places: HTTP headers rendered in error pages, JSON responses consumed by client-side rendering, and API endpoints whose output gets injected into the DOM by frontend code.
Stored XSS
The payload persists on the server and executes when other users view the affected page. This is where the money is. A stored XSS in a user profile field that renders on an admin dashboard can be Critical. Common injection points:
- Display names and profile bios
- Comment and review systems
- File upload metadata (EXIF data, filenames)
- Support ticket content rendered in agent dashboards
- Webhook URLs and callback configurations
- Email subject lines rendered in web-based inboxes
The key insight: the injection point and the execution point are often on different pages, accessed by different user roles. Scanners test the same page they inject into β they miss cross-page stored XSS entirely.
DOM-Based XSS
The payload never reaches the server. Client-side JavaScript reads from a source (URL fragment, localStorage, postMessage) and writes to a dangerous sink (innerHTML, document.write, eval). The server response is clean β the vulnerability exists entirely in the browser.
This is the most underreported XSS type because it requires reading JavaScript source code, not just fuzzing parameters. If you develop the skill to trace source-to-sink data flows in client-side code, you will find bugs that no other hunter and no scanner will catch.
Step 1: Map Every Input Reflection Point
Before you test a single payload, map where user input appears in the application's output. This is not just URL parameters β it includes:
- Query parameters and path segments β the obvious ones
- POST body fields β form submissions, JSON payloads, multipart uploads
- HTTP headers β Referer, User-Agent, X-Forwarded-For rendered in logs or error pages
- Cookie values β sometimes reflected in page content
- URL fragments β never sent to the server, but consumed by client-side JavaScript
Use Burp Suite's passive scanner to flag reflections automatically as you browse. For a more targeted approach, inject a unique canary string (like xss7r4c3) into every parameter and search the response for it. This tells you where your input lands without triggering any WAF rules.
Step 2: Identify the Rendering Context
The same input reflected in different HTML contexts requires completely different payloads. Before crafting an exploit, determine exactly where your input lands:
| Context | Example | Breakout Strategy |
|---|---|---|
| Raw HTML | <div>USER_INPUT</div> | Inject a tag directly: <img src=x onerror=alert(1)> |
| Tag attribute (quoted) | <input value="USER_INPUT"> | Close the attribute and tag: "><img src=x onerror=alert(1)> |
| Tag attribute (unquoted) | <input value=USER_INPUT> | Add an event handler: x onfocus=alert(1) autofocus |
| JavaScript string | var x = 'USER_INPUT'; | Close the string: ';alert(1)// |
| Template literal | var x = `USER_INPUT`; | Expression injection: ${alert(1)} |
| HTML comment | <!-- USER_INPUT --> | Close the comment: --><img src=x onerror=alert(1)> |
| CSS value | background: url(USER_INPUT) | Limited in modern browsers β focus on other contexts |
If you send a generic <script>alert(1)</script> payload into an attribute context, it will fail even if there is zero sanitization. Context-aware payloads are not optional β they are the difference between finding the bug and missing it.
Step 3: Hunt DOM-Based XSS
DOM XSS requires a different methodology. You are not testing server responses β you are auditing client-side JavaScript.
Dangerous Sinks to Search For
Open the browser DevTools, go to Sources, and search the application's JavaScript for these patterns:
innerHTMLβ the most common DOM XSS sinkouterHTMLdocument.write()anddocument.writeln()eval(),Function(),setTimeout(string),setInterval(string)jQuery.html(),jQuery.append(),jQuery.prepend()React dangerouslySetInnerHTMLv-htmlin Vue templates[innerHTML]bindings in Angular
Tainted Sources to Trace
For each sink, trace backward to find where the data originates:
location.hash,location.search,location.hrefdocument.referrerwindow.namepostMessageevent handlerslocalStorage/sessionStoragevalues- URL parameters parsed by client-side routers
If user-controlled data flows from a source to a sink without sanitization, you have a DOM XSS. Burp Suite's DOM Invader extension automates source-to-sink tracing and is worth the Professional license cost for this alone.
Step 4: Bypass WAFs and Sanitizers
Modern applications use WAFs (Cloudflare, AWS WAF, Akamai) and client-side sanitizers (DOMPurify). When your payload gets blocked, don't give up β adapt.
WAF Bypass Techniques
Use alternative tags. WAFs often block <script>, <img>, and <svg> but miss less common tags:
<details open ontoggle=alert(1)>
<math><mtext><table><mglyph><style><!--</style><img src=x onerror=alert(1)>
<svg/onload=alert(1)>
<body onpageshow=alert(1)>
<marquee onstart=alert(1)>
Use alternative event handlers. If onerror and onload are blocked:
onfocus, onblur, oninput, onchange
onanimationstart, onanimationend
onbeforetoggle (new in 2025+)
onpointerover, onpointerenter
Encoding and case tricks:
- HTML entities:
<decoded by the browser after WAF inspection - Double URL encoding:
%253Cdecoded twice by the server - Unicode normalization:
οΌscriptοΌ(fullwidth characters) - Null bytes:
%00between tag characters to confuse pattern matching
Client-Side Sanitizer Bypass (Mutation XSS)
DOMPurify is the gold standard for client-side sanitization, but older versions have known mXSS vectors. When you encounter DOMPurify:
- Check the version (look in the JavaScript source or network responses)
- Search for known bypasses for that version on PortSwigger Research and the DOMPurify GitHub issues
- Test with nested tag combinations that the browser's parser normalizes differently than the sanitizer expects
Mutation XSS is rare but pays extremely well because it bypasses the defense that the application explicitly chose to deploy.
Step 5: Escalate Impact
A report that says "I can execute alert(1)" gets triaged as Low. A report that says "I can take over any user's account" gets triaged as Critical. Same vulnerability, different payout. Always escalate before reporting.
Impact Escalation Paths
- Session hijacking: If cookies lack the HttpOnly flag, steal them with
document.cookieand send to your server. Full account takeover. - Token theft: Many SPAs store JWT tokens in localStorage. XSS can read localStorage and exfiltrate the token.
- Action on behalf of victim: Use
fetch()to perform sensitive actions (change email, change password, transfer funds) as the victim. This works even with HttpOnly cookies because the browser attaches them automatically. - Keylogging: Inject an event listener that captures keystrokes on the page β useful for login pages.
- Phishing: Replace the page content with a fake login form that sends credentials to your server.
Document the full attack chain in your report. Include the payload, the impact proof, and a clear explanation of what an attacker could achieve. See our bug bounty report writing guide for the full template.
XSS Hunting Checklist
- Map all reflection points (parameters, headers, cookies, fragments)
- Inject canary strings to identify where input appears in output
- Determine the rendering context for each reflection
- Test context-specific breakout payloads
- Audit client-side JavaScript for source-to-sink DOM XSS
- Test stored XSS through profile fields, comments, file metadata
- Attempt WAF bypass with alternative tags and event handlers
- Check for mutation XSS if client-side sanitization is present
- Escalate impact: session hijacking, token theft, or action-as-victim
- Write the report with full attack chain and business impact
Tools for XSS Hunting
| Tool | Purpose | Cost |
|---|---|---|
| Burp Suite Professional | Passive reflection detection, active scanning, Turbo Intruder for payload fuzzing | $449/yr |
| DOM Invader (Burp extension) | Automated source-to-sink tracing for DOM XSS | Included with Burp Pro |
| dalfox | Open-source XSS scanner with context-aware payload generation | Free |
| XSStrike | Fuzzing engine that analyzes context and generates targeted payloads | Free |
| Browser DevTools | JavaScript debugging, DOM inspection, network analysis | Free |
| kxss | Fast reflection checker β identifies which parameters reflect unfiltered | Free |
For a deeper look at Burp extensions that accelerate XSS hunting, see our Burp Suite extensions guide.
Common Mistakes That Waste Time
- Testing generic payloads without checking context. If your input lands inside a JavaScript string, no amount of
<script>tags will work. Check context first. - Ignoring DOM XSS. If you only test server-reflected parameters, you miss an entire class of vulnerabilities that no other hunter is looking at.
- Reporting self-XSS. If the victim must paste the payload into their own browser console, it is not a vulnerability. The payload must be deliverable via a link, stored content, or postMessage.
- Stopping at alert(1). Prove impact. The difference between a $200 payout and a $5,000 payout is the escalation chain.
- Fighting the WAF with brute force. Sending 10,000 payloads from a wordlist triggers rate limiting and gets your IP blocked. Understand what the WAF filters, then craft one payload that bypasses it.
What to Read Next
XSS hunting pairs well with other client-side and injection-focused skills:
- Bug Bounty Recon Workflow β find the injectable endpoints before other hunters do
- API Hacking for Bug Bounty β APIs often reflect input in JSON responses consumed by frontend code
- Race Condition Hunting β another manual-only vulnerability class that scanners miss
- SSRF Hunting for Bug Bounty β server-side injection that pairs with XSS for full-chain exploits
- Bug Bounty Resource Center β the complete index of our hunting guides