Kodokon kodokon.com

HTML-side security: XSS, sandbox, noopener and CSP

Identify the XSS vectors specific to markup and deploy HTML's native defenses: sandbox, noopener and Content Security Policy.

11 min · 3 questions

Open this lesson in Kodokon

The escaping context makes all the difference: a value that is harmless in a text node becomes executable in an attribute. The unquoted attribute is the worst case: a single space in the injected data is enough to create a new attribute, for example an onmouseover handler. Even when properly quoted, a value remains dangerous in sensitive sinks: href and src accept the javascript: scheme, formaction hijacks a form's submission, and srcdoc interprets its entities as HTML once decoded.

HTML
<!-- userName = x onmouseover=alert(1) -->
<img src="avatar.png" alt=x onmouseover=alert(1)>
<!-- The unquoted attribute lets a handler in -->
Unescaped data in an unquoted attribute becomes code.

An iframe's sandbox attribute strips all privileges, and you then grant them back one by one: allow-scripts, allow-forms, allow-popups... Without allow-same-origin, the embedded document receives an opaque origin: no cookies, no storage, no access to the parent DOM - even if it is served from your own domain.

HTML
<iframe
  src="https://widget.example.com"
  sandbox="allow-scripts allow-forms"
  referrerpolicy="no-referrer">
</iframe>
A third-party widget that can run scripts, yet confined to an opaque origin.

A target=_blank link historically gave the opened page a window.opener reference to your tab: it could redirect your page to a phishing clone, the attack known as tabnabbing. Modern browsers now apply noopener implicitly on target=_blank, but spelling it out remains essential for window.open in JavaScript and to document the intent; add noreferrer to also suppress the Referer header.

HTML
<a href="https://external.example"
   target="_blank"
   rel="noopener noreferrer">
  View the external resource
</a>
The opened page receives window.opener === null.

A Content Security Policy restricts the sources of executable code and neutralizes most XSS, even when the injection has succeeded. Declared through a <meta http-equiv> tag, it nevertheless has blind spots: the frame-ancestors, report-uri and sandbox directives are ignored there and require the HTTP header. Prefer nonces regenerated on every response over domain allowlists, which the strict-dynamic directive makes obsolete anyway.

HTML
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self';
               script-src 'self' 'nonce-r4nd0m'">
Only scripts carrying the server's nonce run.

Knowledge check

Make sure you remember the key points of this lesson.

  1. Why does combining allow-scripts and allow-same-origin on a same-origin iframe cancel the sandbox?
    • The embedded document, scriptable and not isolated, can reach the parent DOM and remove the sandbox attribute itself
    • The two tokens are incompatible and the browser ignores the whole attribute
    • allow-same-origin automatically disables all the other tokens
  2. Which attack does rel=noopener protect against?
    • Theft of session cookies by the opened page
    • Script injection into the opened page
    • Tabnabbing: the opened page uses window.opener to redirect your tab to a phishing site
  3. Which directive is ignored when the CSP is declared through <meta http-equiv>?
    • script-src
    • frame-ancestors
    • default-src
    • img-src