Injection Contexts

Chapter 3
Injection Contexts
HTML, attribute, JavaScript, URL, and CSS — why each needs different handling

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:

ContextExample positionDangerous 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).

<div> ...user data... </div> <script>alert(1)</script> // injected tag runs &lt;script&gt;alert(1)&lt;/script&gt; // encoded -> shown as text

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"> // payload: "><script>alert(1)</script> OR " onmouseover="alert(1) <input value="" onmouseover="alert(1)"> // event handler fires
Unquoted attributes are far more dangerous — always quote
If an attribute value isn't wrapped in quotes (<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:

<script> var name = "HERE"; </script> // payload: "; steal(); // var name = ""; steal(); //"; // breaks the string, runs code
Don't put untrusted data into a JS context — and HTML-encoding won't save you
Inside JavaScript, HTML entity encoding does not protect you: the JS parser doesn't decode &quot;, 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:

<a href="HERE">click</a> // payload: javascript:alert(document.cookie) <a href="javascript:alert(document.cookie)"> // runs on click

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

"Escaping" is not one thing — it's five
The most common XSS mistake among developers who think they're safe is applying HTML encoding everywhere. HTML-encoding data that lands in a JavaScript context, or a URL scheme, or an unquoted attribute, does not stop the attack — each context decodes differently. The correct discipline (Chapter 6) is to encode for the specific context the data lands in: HTML-entity-encode for HTML body, attribute-encode (with quotes) for attributes, JavaScript-string-encode (or avoid) for JS, scheme-validate + URL-encode for URLs, and avoid/allowlist for CSS. One input rendered in three places may need three different encodings.

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

Exercise 1

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.

📄 View solution
Exercise 2

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.

📄 View solution
Exercise 3

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.

📄 View solution

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() & legacy expression() 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)