Lion Cipher Lab
Eliminate DOM-based XSS with Trusted Types and a strict CSP
- AppSec / DevSecOps
- Advanced
- ~70 min
- Free · no account required
Why it matters
The beginner XSS lab teaches the defense that works when the malicious data passes through your server: encode it on the way out, lock down execution with a Content Security Policy, and reflected/stored XSS dies at the template. But there is an entire class of XSS that never gives your server the chance — DOM-based XSS. The payload lives in a URL fragment (`#...`), a query string read by JavaScript, a `postMessage`, or `window.name`; the browser never sends the fragment to the server at all, and your carefully-encoded server response is irrelevant because the danger happens AFTER the page…
What you'll learn
- naming DOM-based XSS as a distinct class from reflected/stored XSS: the taint never reaches the server, so server-side output encoding cannot see it
- identifying a DOM SINK — a browser API (innerHTML, document.write, eval, the Function constructor, script.src) that turns a string into live markup or code
- building and locally serving a deliberately-vulnerable static page and proving a `#`-fragment payload executes JavaScript in your own browser
- reading the DOM XSS data flow: a SOURCE (location.hash / URL) flowing untrusted into a sink with no sanitisation
- turning on Trusted Types with the response header `Content-Security-Policy: require-trusted-types-for 'script'` and watching the sink throw a TypeError instead of executing
- authoring a named Trusted Types policy with trustedTypes.createPolicy() whose createHTML runs DOMPurify, so legitimate markup renders while injection is blocked
- restricting which policy names are allowed with the CSP `trusted-types` directive (and why `'allow-duplicates'` and a default policy are levers, not defaults)
- rolling out safely with Content-Security-Policy-Report-Only + a report endpoint, reading the violation reports before you enforce
- enumerating the full set of DOM sinks Trusted Types guards, and knowing which it does NOT (it guards injection sinks, not navigation like location.href)
- the judgment: Chromium-only support means Trusted Types is defense-in-depth, why 'sanitize at the sink' beats 'sanitize at input', and the danger of a permissive default policy
Steps in this lab
- Why server-side encoding can't touch this bug: DOM-based XSS
- Build the deliberately-vulnerable page and a header-setting local server
- Prove the DOM-XSS fires (and meet DevTools)
- Turn on Trusted Types and watch the sink throw a TypeError
- Write a named Trusted Types policy that sanitises with DOMPurify
- Lock down which policy NAMES are allowed with the trusted-types directive
- Roll out safely: Report-Only mode, a report endpoint, and enumerating every sink
- The judgment: when Trusted Types is right, when it isn't, and how a pro ships it