August 13, 2026
How I Systematically Find XSS Bugs in Bug Bounty Programs (Step-by-Step Method)
A repeatable recon-to-report process for hunting Cross-Site Scripting ,the vulnerability class that still pays out more than almost…

By b0dj0x
7 min read
A repeatable recon-to-report process for hunting Cross-Site Scripting ,the vulnerability class that still pays out more than almost anything else in 2026
Cross-Site Scripting doesn't get less common just because it's old. It shows up in new JavaScript frameworks, in half-finished sanitization functions, in "we'll fix it later" input fields, and in the thousand small corners of a modern web app that nobody code-reviews carefully. If you can learn to hunt it systematically instead of just throwing <script>alert(1)</script> at every box you see, you'll out-perform 90% of people who call themselves bug hunters.
This is the process I actually use. No fluff, no "just try random payloads and hope." Every step has a purpose.
Before anything else:_ only test targets you're explicitly authorized to test — a company's published bug bounty program, a private invite, or your own lab. Testing without authorization is illegal, full stop, regardless of intent._
Step 1: Understand what you're actually looking for
XSS happens when user-controlled input ends up rendered by a browser without being properly neutralized. There are three flavors, and you need to think about them differently:
- Reflected XSS — input from the request (URL parameter, form field, header) is echoed straight back into the page's response.
- Stored XSS — input gets saved server-side (a comment, a profile bio, a support ticket) and rendered later for other users. Higher severity, because it can hit victims who never clicked a malicious link.
- DOM-based XSS — the vulnerability lives entirely in client-side JavaScript. The server may never see the payload; it's JS reading from
location.hash,document.referrer,postMessage, or similar, and writing it into the DOM unsafely.
Most beginners only hunt the first type. The real money is increasingly in the second and third.
Step 2: Recon before you touch a single payload
You cannot test what you haven't found. Before injecting anything:
- Enumerate the attack surface. Pull subdomains (
subfinder,assetfinder,amass), then check which are alive (httpx). A forgotten staging subdomain is often less hardened than the main app. - Pull historical and current URLs.
gauandwaybackurlssurface old parameters and endpoints that current crawlers miss — including ones devs forgot were still reachable. - Extract JS files and read them. Run
linkfinderor manually grep bundled JavaScript for API endpoints, hidden parameters, andinnerHTML/document.write/evalusage — these are your DOM-XSS candidates. - Map every input. Not just search boxes. URL parameters, form fields, HTTP headers (User-Agent, Referer, X-Forwarded-For), file upload names, JSON body fields, WebSocket messages, and query strings buried in single-page-app routing.
Treat this like building a target list, not a to-do item you rush through.
Step 3: Find reflection points first
For every input you mapped, send a unique, harmless marker string — something like xssTEST12345 — and watch where it reappears in the response.
Ask three questions about every reflection:
- Where does it land? Inside an HTML tag body? Inside an attribute? Inside a
<script>block? Inside a JS string, a JSON blob, a URL, or a CSS value? The context determines which characters you actually need to break out. - What gets filtered or encoded? Try < > " ' ( ) ; = / individually and see which ones survive untouched. This tells you exactly what the filter is (and isn't) doing.
- Does it reflect once or multiple times? Multiple reflection points in different contexts on the same page sometimes let you chain a bypass that no single point would allow alone.
This step is where most people get lazy and skip straight to firing payloads. Don't. Understanding why a payload would work is what lets you adapt when the obvious one gets blocked.
Step 4: Craft context-aware payloads
A payload that works inside a raw HTML body will usually fail inside a JS string or an HTML attribute, and vice versa. Match the payload to the context you found in Step 3:
- HTML body context: something like
<img src=x onerror=alert(document.domain)>— event handlers are more reliable than<script>tags, which many frameworks block from executing when injected viainnerHTML. - Attribute context: you often need to break out of the quote first, e.g. closing a
value="..."attribute before adding an event handler. - JavaScript string context: you need to close the existing string and statement cleanly, without triggering a syntax error that kills the rest of the page (which would also alert the developers).
- URL/href context: check whether
javascript:URIs are still accepted inhreforsrcattributes — an old but still-common oversight.
Use document.domain or a unique alert message instead of a generic alert(1) — it makes your proof-of-concept unambiguous when you screenshot it for the report, and it's the first thing a triager checks to rule out a false positive.
Step 5: Don't neglect DOM-based XSS
This is where the JS-reading skills from Step 2 pay off. Search the page's scripts (view-source, or Burp's search-in-response feature) for sources — places untrusted data enters — flowing into sinks — places that render it unsafely:
- Sources:
location.hash,location.search,document.referrer,window.name,postMessagelisteners. - Sinks:
innerHTML,outerHTML,document.write,eval,setTimeoutwith a string argument,insertAdjacentHTML.
If you see a source flowing into a sink with no sanitization in between, you likely have DOM XSS — and it often won't show up in a server-side scan at all, because the payload never leaves the browser.
Step 6: Automate the boring parts, not the thinking
Automation should widen your coverage, not replace your judgment.
- Dalfox — purpose-built XSS scanner, good at parameter discovery plus payload testing in one pass.
- XSStrike — smart about context detection and generates payloads based on what it sees, rather than firing a static list.
- Burp Suite (Repeater + Intruder) — for manual, context-aware testing once you've narrowed down a promising input. This is where real bugs get confirmed, not in the scanner.
- ParamSpider / Arjun — for surfacing parameters you'd otherwise miss on large targets.
Run the scanners across your whole recon list to flag candidates, then go manual on anything interesting. Scanners are good at breadth; you're better at depth.
Step 7: When your payload gets blocked
Filters and WAFs are the norm, not the exception, on any program worth hunting. When your first payload dies:
- Test each special character in isolation to map exactly what's being stripped, encoded, or blocked — don't guess.
- Try alternate encodings (HTML entities, case variation, whitespace/tab tricks inside tags) to see if the filter is doing simple string matching rather than proper parsing.
- Look for alternate event handlers and tags beyond the obvious ones — filters that block
<script>andonerroroften forget less common vectors. - If the app uses a JS framework (React, Vue, Angular), check whether there's a
dangerouslySetInnerHTML,v-html, or[innerHTML]binding somewhere — frameworks that auto-escape by default create false confidence, and devs sometimes explicitly opt out of that protection without realizing the risk.
The goal isn't a magic bypass string — it's understanding the filter's logic well enough that a working payload becomes obvious.
Step 8: Prove real impact, not just an alert box
alert(1) gets you a "informative" or "won't fix" from a lot of triagers now — it's expected to be table stakes, not proof of severity. Go further:
- Demonstrate cookie/session token theft (in a safe, self-contained PoC — never exfiltrate real user data on a live target).
- Show account takeover potential: can the payload perform actions as the victim, like changing their email or issuing an API token?
- For stored XSS, show it firing in an admin or privileged context — that's usually what pushes a report from Medium to High/Critical.
- Chain it with something else where relevant (CSRF token theft, IDOR) to show a realistic attack path.
Severity in most bounty programs is judged on business impact, not on the payload's cleverness.
Step 9: Write a report that gets paid, not ignored
A great finding with a bad report gets triaged slowly or downgraded. Structure every report the same way:
- Title — specific and severity-obvious: "Stored XSS in Profile Bio Field Executes in Admin Context."
- Summary — two or three sentences: what it is, where it is, what it affects.
- Steps to reproduce — numbered, exact, no ambiguity. Include the literal payload, the exact field, and any account states needed (e.g., "log in as User A, then log in as User B to view").
- Proof of concept — screenshot or short screen recording showing the payload firing.
- Impact — spelled out concretely: what an attacker gains, which users are affected, whether PII is exposed.
- Suggested fix — optional, but shows professionalism: output encoding, CSP, sanitization library, etc.
Triagers read dozens of reports a day. Make yours the one that requires zero back-and-forth to understand.
Common mistakes that waste your time
- Testing only the most obvious fields (search bars) and ignoring headers, JSON bodies, and file names.
- Giving up after the first blocked payload instead of mapping the filter.
- Reporting duplicate, well-known payloads on over-tested public programs instead of going deeper on a narrower scope.
- Relying entirely on scanners and skipping manual verification — false positives waste a triager's trust in your future reports.
- Skipping DOM-based XSS entirely because it doesn't show up in server logs the way reflected/stored does.
Turning this into a Medium article that actually gets read
If you're writing this up for Medium, the technical quality gets you credibility — but structure decides whether anyone finds it. A few things that consistently matter:
- Front-load the hook. Medium only shows your title, subtitle, and the first couple of lines in the feed. Open with a specific claim or result, not a generic definition of XSS.
- Target length. Long-form technical guides in this niche tend to land around 1,800–3,000 words — enough to be genuinely useful, not so long it becomes a slog.
- Structure for skimmability. Short paragraphs, numbered steps, bold key terms. Most readers decide whether to keep reading based on how the page looks before they read a word.
- Use a real, specific title. "How I Find XSS in Bug Bounty Programs (Step-by-Step)" outperforms "Understanding Cross-Site Scripting" — specificity and a personal angle both help.
- Tag deliberately. Use tags that match both your niche and Medium's broader curated topics (e.g.,
Cybersecurity,Bug Bounty,Ethical Hacking,Programming,Infosec) — five tags, mixing narrow and broad, gives you the best surface area. - Add a cover image. It's a large share of your click-through rate in the Medium feed and on social — a clean, relevant graphic beats a stock photo.
- End with a concrete next step, not just a sign-off — point readers to a lab (PortSwigger's Web Security Academy, OWASP Juice Shop) so the CTA has somewhere to go.
None of that replaces having something genuinely useful to say — but paired with real technical depth, it's what separates an article that sits at twelve views from one that gets picked up and shared.
Disclaimer: This guide is for use on authorized bug bounty programs, CTFs, and your own lab environments only. Testing systems without permission is illegal in most jurisdictions, regardless of intent.
By b0dj0x.cc