Skip to main content
XSS happens when user input ends up in a page without being properly sanitized, letting an attacker inject JavaScript that runs in another user’s browser, in their session, with their cookies, their DOM access, everything. It’s a client-side bug, but it’s one of the few web vulnerabilities that routinely leads straight to account takeover, since the victim’s browser does the attacker’s work for them.

Stored XSS

Payload gets saved server-side (a comment, a profile field) and fires for every visitor who views that page later. Highest impact, hits anyone who views the page.

Reflected XSS

Payload comes back in the response immediately, usually from a URL parameter or search box. Needs a victim to click a crafted link.
DOM-based XSS is really a third axis, not a competitor to the above: the payload never touches the server at all, a client-side script reads attacker-controlled data (location.hash, document.URL, a URL parameter) and writes it into a dangerous sink (innerHTML, eval, document.write) itself. It can be either stored or reflected in terms of where the data originates, but the vulnerability lives entirely in JavaScript, not server-side templating.

Basic Payloads

Start simple and work up, a bare <script> tag confirms the sink executes JavaScript at all before worrying about filters.
If you’re injecting into an existing HTML attribute, you need to break out of the quote first.
When <script> itself gets stripped or blocked, an event handler on a tag that’s still allowed does the same job.
window.origin in the alert is deliberate, it proves the script runs in the target’s own origin (and can therefore read that origin’s cookies/storage), not just that a popup fired. Always confirm origin before chasing a payload further.

Context Matters

Where your input lands changes which payload works. Check which context you’re in before reaching for a generic script tag.
Input renders as a plain HTML tag, inject a new element directly.

Filter and WAF Bypasses

Event handlers on tags that often survive a blocklist:
Avoiding the literal string alert with bracket notation or fromCharCode:
If quotes are stripped entirely, String.fromCharCode and backticks both avoid needing any:
Browsers (Gecko especially) tolerate a surprising range of non-alphanumeric characters between an event name and =:
Tabs, newlines, and carriage returns can be inserted inside the word “javascript” without breaking how the browser parses the protocol, since only those three characters get silently stripped from a URL scheme:
HTML entity encoding (decimal or hex, with or without the trailing semicolon) slips past filters that only check for a literal javascript: string:
Built to fire regardless of exactly which context (HTML, attribute, JS string, URL) it lands in, useful when you’re blind-testing many injection points at once and don’t want to craft a payload per field:
See also Polygot XSS - Payloads All The Things for more commands

Stealing Cookies

Loading a remote script is the cleanest way to deliver a larger payload without cramming everything into the injection point itself.
A one-liner that exfiltrates the victim’s cookies to a listener via an image request, works even when the target blocks outbound <script> loads but still renders images.
1

Stand up a collector

A tiny PHP page logs every cookie it receives, keyed by the victim’s IP.
2

Write the stealer script

3

Deliver it through the injection point


DOM-Based XSS and Local File Read

When the vulnerable code lives entirely client-side, the usual suspects are reading from a source the attacker controls and writing it into a dangerous sink without sanitizing in between.
Sources (attacker-influenced input)Sinks (dangerous if unsanitized)
location.hash, location.searchinnerHTML, outerHTML
document.URL, document.referrerdocument.write() / document.writeln()
window.nameeval(), setTimeout(string), setInterval(string)
URL parameters read via JSelement.setAttribute() on event/href attributes
HTML injection inside a server-rendered PDF (via a headless browser like wkhtmltopdf or a Chrome-based renderer) is a DOM-XSS-adjacent bug worth knowing: if user input reaches the HTML that gets rendered into a PDF, the renderer’s own JavaScript engine can be abused for local file read or SSRF, since the renderer often runs with far fewer restrictions than a normal browser tab. See SSRF to local file read through HTML injection in a PDF and finding SSRF via HTML injection inside a PDF on AWS EC2 for real-world writeups.
If the renderer’s JS engine allows XMLHttpRequest against the file:// scheme (several headless PDF renderers historically did), this reads a local file and writes its contents straight into the rendered output.

Automated Tools

Fuzzes a parameter intelligently, builds its own payloads based on what the app reflects back rather than just throwing a static list.
XSStrike, BruteXSS, and XSSer cover most automated-detection needs between them, XSStrike is generally the strongest first choice for its context-aware fuzzing.

Quick Reference Tools

HackTricks: XSS

Deep, continuously updated collection of payloads and bypasses by context.

PayloadsAllTheThings: XSS

Large categorized payload collection.

xss-payload-list

Flat all-in-one payload list, good for quick fuzzing.

OWASP XSS Filter Evasion Cheat Sheet

The canonical reference for encoding and filter-bypass tricks.

XSStrike

Context-aware XSS detection and fuzzing.

XSSer

Automated XSS detection with an optional GTK UI.
Cookie stealers and anything that exfiltrates data to an external listener are active exploitation, not just a proof of concept. Make sure that’s explicitly in scope before moving past a plain alert().