Chapter 68: Preventing XSS & Clickjacking

Contextual Output Encoding & Escaping

Context-Aware Encoding Rules Across HTML Body, HTML Attributes, JavaScript Literals, CSS, and URI Contexts

LEARNING OBJECTIVES
  • Understand why "one-size-fits-all" encoding fails and how browser parsers transition across execution contexts.
  • Implement precise contextual encoding algorithms for HTML Body, HTML Attributes, JavaScript literals, URIs, and CSS.
  • Prevent </script> tag breakout attacks in server-side JSON state hydration pipelines.
  • Build a robust multi-context sanitization and encoding utility.
🎬 INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
🌐
1. Input
Directives & Tags
⚙️
2. Parse
Tokenizer & AST
🌳
3. Layout
Box Model & Flow
🎨
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

📖 The Mental Model & Story (Intuitive Foundation)

Imagine a diplomatic passport carrying a message across five international borders: France, Germany, China, Russia, and Japan.

If you translate an English message into German, it will be understood in Berlin. But if you hand that same German document to a border guard in Tokyo who only speaks Japanese, the meaning is corrupted or misinterpreted. Worse, if a malicious phrase sounds harmless in French but acts as a treasonous command in Russian, you trigger a catastrophe at the Russian border.

In web browsers, a single document contains multiple sub-languages with totally different syntax rules: HTML, CSS, JavaScript, and URIs.

Converting < into &lt; protects an HTML body tag (<div>&lt;script&gt;</div>). However, if you inject that exact same string into a JavaScript variable inside a <script> tag (const user = '&lt;script&gt;';), the JavaScript engine does not decode HTML entities; it treats &lt; as literal characters, but if the attacker enters "; alert(1); //, the HTML encoder leaves quotes intact and the script executes!

Encoding must be strictly contextual: the transformation algorithm must match the exact parser that will interpret that specific slice of text.


Technical Deep Dive & Specifications

The 5 Web Parser Contexts

When a browser renders a web page, its tokenizer constantly switches states between distinct parsers:

+-------------------------------------------------------------------------------+
|                       THE 5 BROWSER ENCODING CONTEXTS                         |
+-------------------------------------------------------------------------------+

 1. HTML BODY CONTEXT:
    <div>[USER DATA HERE]</div>
    -> Rule: Encode HTML special characters (&, <, >, ", ') to named/numeric entities.

 2. HTML ATTRIBUTE CONTEXT:
    <input type="text" name="fname" value="[USER DATA HERE]">
    -> Rule: Attribute values MUST be quoted; encode quotes, ampersands, and angle brackets.

 3. JAVASCRIPT VARIABLE / JSON CONTEXT:
    <script>const state = "[USER DATA HERE]";</script>
    -> Rule: Unicode/Hex escape quotes, backslashes, and explicitly escape '</script>'.

 4. URI / URL CONTEXT:
    <a href="/search?q=[USER DATA HERE]">Search</a>
    -> Rule: Percent-encode (RFC 3986) via encodeURIComponent; validate scheme!

 5. CSS PROPERTY CONTEXT:
    <div style="color: [USER DATA HERE];">Text</div>
    -> Rule: Strict alphanumeric allowlisting or CSS hex escaping (\3C ).

Contextual Encoding Specifications Matrix

Context Example HTML Location Attack Syntax Breakout Required Encoding / Strategy Safe Output Example
1. HTML Body <div>DATA</div> <script>, <img> &&amp;
<&lt;
>&gt;
"&quot;
'&#x27;
&lt;b&gt;Hello&lt;/b&gt;
2. Quoted Attribute <input value="DATA"> " onfocus="alert(1) All HTML body entities plus ASCII hex for quotes &quot; onfocus=&quot;...
3. Unquoted Attribute <input value=DATA> [space] onfocus=... NEVER USE UNQUOTED ATTRIBUTES (Space, Tab, Newline, >, = break attributes) Always quote attributes!
4. JavaScript String <script>let x = 'DATA';</script> '; alert(1); // or </script> Unicode-escape characters: \\\
"\u0022
'\u0027
<\u003C
\u0027; alert(1);
5. URI Parameter <a href="/profile?id=DATA"> &id=2 or javascript:... encodeURIComponent(DATA) john%20doe%26admin%3Dtrue

The </script> Tag Breakout Vulnerability in JSON Hydration

A pervasive vulnerability in Single Page App (SPA) server-side rendering (Next.js, Nuxt, Remix) occurs when embedding server state directly into HTML:

<!-- ❌ INSECURE SSR HYDRATION -->
<script>
  window.__INITIAL_STATE__ = <%= JSON.stringify(untrustedData) %>;
</script>

Why JSON.stringify() is NOT enough: The HTML parser has higher precedence than the JavaScript engine. When the HTML parser reads inside a <script> tag, it scans strictly for the closing sequence </script> (case-insensitive).

If untrustedData contains: "</script><script>alert('XSS')</script>", JSON.stringify() produces:

<script>
  window.__INITIAL_STATE__ = "<\/script><script>alert('XSS')<\/script>";
</script>

The HTML tokenizer encounters </script>, immediately terminates the script block, and interprets <script>alert('XSS')</script> as a brand-new live executable script tag!

The Fix: Serialization Escaping

Replace < and / characters with their Unicode equivalents:

function safeJsonStringify(data) {
  return JSON.stringify(data)
    .replace(/</g, '\\u003C')
    .replace(/>/g, '\\u003E')
    .replace(/\//g, '\\u002F')
    .replace(/\u2028/g, '\\u2028') // Line separator (JS syntax error)
    .replace(/\u2029/g, '\\u2029'); // Paragraph separator (JS syntax error)
}

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL example.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

💻 Interactive Code Playground

Starter Code

The following application provides a live multi-context encoder tool, demonstrating how the same payload must be encoded differently depending on where it will be placed.

Line-by-Line Code Breakdown

  • Line 33–42: htmlBody method replaces the 5 critical HTML syntax characters with named XML entities (&amp;, &lt;, &gt;, &quot;, &#x27;).
  • Line 52–65: jsString converts quotation marks, backslashes, and angle brackets into 4-digit hexadecimal Unicode escape sequences (\u003C, \u0022), preventing string termination and tag breakouts.
  • Line 67–69: uriComponent delegates to native encodeURIComponent(), translating spaces to %20 and symbols to percent-encoded octets.
  • Line 76: When outputting JS context, note how </script> is safely escaped to <\/script> in string literals.

Expected Browser Render Output

For the input payload <script>alert("XSS & 'pwned'");</script>:

  • HTML Body: &lt;script&gt;alert(&quot;XSS &amp; &#x27;pwned&#x27;&quot;);&lt;/script&gt;
  • HTML Attribute: <input type="text" value="&lt;script&gt;alert(&quot;XSS &amp; &#x27;pwned&#x27;&quot;);&lt;/script&gt;">
  • JavaScript Context: <script> const userPayload = "\u003Cscript\u003Ealert(\u0022XSS & \u0027pwned\u0027\u0022);\u003C\u002Fscript\u003E"; </script>
  • URI Component: <a href="https://example.com/search?q=%3Cscript%3Ealert(%22XSS%20%26%20'pwned'%22)%3B%3C%2Fscript%3E">Link</a>

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

🏋️ Hands-On Exercise

🎯 The Challenge: Fix a Broken Multi-Context Profile Card

Instructions:

  1. You are given a script that renders a user profile card containing:
    • A username displayed in an HTML <h3> tag.
    • An email placed inside an <input value="..."> attribute.
    • A user preference JSON object placed inside an inline <script> tag.
    • A personalized homepage URL placed inside an <a href="..."> link.
  2. The current code applies HTML entity encoding everywhere, which breaks the JavaScript and URI contexts while leaving the SSR script block vulnerable to breakout.
  3. Fix the rendering function so that each field uses its appropriate contextual encoder and URL scheme validator.

🏁 Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

⚠️ Common Pitfalls

  1. HTML Entity Encoding Inside JavaScript Code: Writing var data = "&lt;script&gt;";. In JavaScript, &lt; is not decoded to <; it remains literal text. However, if the data contains unescaped quotes ("), the HTML encoder ignores it and your JS string breaks.
  2. Double-Encoding Errors: Running an already-encoded string through an encoder a second time (e.g., &amp; becomes &amp;amp;). Track whether data is in raw or encoded state through strict typing.
  3. Unquoted HTML Attributes: Writing <div class=${userInput}>. If userInput contains spaces (e.g., foo onmouseover=alert(1)), the browser interprets the space as the delimiter between attributes, creating a live event handler.

💡 Pro Tips

  1. Use serialize-javascript for SSR Hydration: In Node.js / Next.js production backends, use the battle-tested serialize-javascript library from Yahoo to serialize server state safely.
  2. Automate Contextual Escaping with Template Engines: Modern template engines like Mustache, Handlebars, and JSX automatically apply HTML context escaping; ensure developers do not bypass them with triple braces {{{ raw }}} or v-html.

📌 Key Takeaways

  • There is no single universal encoding algorithm; transformations must be context-aware.
  • The 5 core web contexts are HTML Body, HTML Attribute, JavaScript Literal, URI Parameter, and CSS Property.
  • JSON.stringify() alone is insufficient for embedding data into inline <script> tags because the HTML tokenizer looks for </script> before JS is parsed.
  • Always quote HTML attribute values (value="..."); unquoted attributes allow attribute injection via simple spaces.
  • Validate URI schemes (http:, https:) before placing user inputs into href or src attributes.
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

Why does JSON.stringify({ note: "</script><script>alert(1)</script>" }) trigger an XSS vulnerability when rendered directly inside an inline <script> tag?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

What is the primary danger of leaving an HTML attribute value unquoted (e.g., <input value=USER_INPUT>)?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

Which encoding format is correct when rendering dynamic user data inside a JavaScript string literal inside a <script> tag?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP