Injection Contexts
The same input can be harmless in one place and a script-execution hole in another. Where untrusted data lands in the page — its context — determines which characters are dangerous and therefore how it must be encoded. This is the most important technical idea in XSS defence: encoding is context-dependent, and getting the context wrong reopens the hole even when you "escaped" the input.
The Five Contexts
A browser parses a page through several sub-grammars, and untrusted data can be injected into any of them. Each has different "breakout" characters:
| Context | Example position | Dangerous characters |
|---|---|---|
| HTML body | <div>HERE</div> | < > & |
| HTML attribute | <input value="HERE"> | " ' (quote breakout) + < > |
| JavaScript | <script>var x="HERE"</script> | " ' \ newlines, </script> |
| URL | <a href="HERE"> | javascript: scheme, " ' |
| CSS | <style>… HERE …</style> | " ' ( ), expression(), url() |
Context 1: HTML Body
Data between tags. The breakout is simply introducing a new tag — the canonical XSS. Encoding < > & to entities neutralizes it (Chapter 1's example).
Context 2: HTML Attribute
Data inside an attribute value. The attacker doesn't need a new tag — they just break out of the quotes and add an event-handler attribute:
<input value=HERE>), the attacker doesn't even need a quote character to break out — a single space ends the value and lets them add onmouseover=alert(1). Unquoted attributes massively expand the dangerous-character set and are a frequent source of XSS. Rule: always quote attribute values, and HTML-attribute-encode the data inside (including quotes). Event-handler attributes (onclick, onerror, …) and certain attributes (href, src, style) are especially risky landing spots.
Context 3: Inside JavaScript
Data placed into a script block (e.g. a server templating a value into JS). This is the most dangerous context — the attacker is already in a script and only needs to break the string literal:
", and there are many breakout vectors (quotes, backslashes, </script> which closes the block from inside a string, newlines, Unicode separators). The robust answer is: don't template untrusted data directly into JavaScript. Instead, put it in the DOM as data (e.g. a data- attribute or JSON.parse of a properly JSON-and-HTML-encoded value) and read it from JS. If you must inline it, use a strict JavaScript-string encoder (e.g. \xHH hex escaping) — but avoiding the context is the real fix.
Context 4: URL
Data used as a URL, typically in href or src. Even with quotes intact, the attacker can supply a dangerous scheme:
Attribute-encoding the quotes isn't enough here — javascript: contains no special HTML characters. The fix is to validate the scheme: allow only http:, https:, mailto: (an allowlist), and reject javascript:, data:, vbscript:. Then URL-encode the data.
Context 5: CSS
Data placed into a stylesheet or style attribute. Historically expression() (old IE) ran JS; today the main risks are url() exfiltration, breaking out of the CSS context into HTML, and UI-redress tricks. Untrusted data in CSS should generally be avoided or strictly allowlisted (e.g. a fixed set of colour names), never freely templated.
The Central Lesson: Match the Encoder to the Context
A practical corollary: nested contexts compound. A value inside an onclick handler is in a JS context inside an HTML-attribute context inside HTML — it needs JS encoding then attribute encoding. These multi-layer cases are exactly why "just use the framework's auto-escaping" (Chapter 9) is the safer real-world advice than hand-encoding.
Hands-On Exercises
For each injection point, write a payload that would execute and name the breakout: (a) <div>HERE</div>; (b) <input value="HERE">; (c) <input value=HERE> (unquoted); (d) <a href="HERE">. State which dangerous character/scheme each relies on.
Explain why HTML entity encoding fails to protect a value placed inside a <script> block, giving a concrete breakout. Then state the recommended approach (don't template into JS; pass via a data attribute / JSON) and why it's safer.
A developer HTML-encodes one username value and reuses it in three places: HTML body, an href, and an onclick handler. Identify which placements are still vulnerable despite the HTML encoding, and give the correct encoding/validation for each context.
Chapter 3 Quick Reference
- Context = where untrusted data lands; it decides the dangerous characters and the required encoding
- HTML body — danger
< > &; fix: HTML-entity encode - HTML attribute — danger quotes (breakout) +
< >; fix: quote the value + attribute-encode - Unquoted attribute — a space breaks out; far more dangerous — always quote
- JavaScript — danger quotes,
\,</script>; HTML-encoding doesn't help; don't template into JS — use data attrs/JSON - URL — danger
javascript:/data:schemes; fix: scheme allowlist (http/https/mailto) + URL-encode - CSS — avoid/allowlist untrusted data;
url()& legacyexpression()risks - Core lesson: match the encoder to the context — HTML-encoding everywhere is the classic false-safety mistake; nested contexts compound
- Next chapter: anatomy of a payload — breakouts, event handlers, bypasses, and polyglots (defensive framing)