Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
Academy DOM-based XSS
Intermediate · 20 min

DOM-based XSS

Learn how JavaScript sources like location.hash feed dangerous sinks like innerHTML to enable client-side XSS.

1 DOM XSS Sources and Sinks

DOM-based XSS occurs entirely in the browser. Attacker-controlled data flows from a DOM source to a dangerous sink without server involvement, bypassing server-side sanitization.

Common sources:

  • location.hash, location.search, location.href
  • document.referrer
  • window.name
  • localStorage, sessionStorage
  • postMessage event data

Dangerous sinks:

// These sinks interpret strings as HTML/JS — DANGEROUS with user data
element.innerHTML = userInput;
element.outerHTML = userInput;
document.write(userInput);
eval(userInput);
new Function(userInput);
location.href = userInput;  // javascript: URI
scriptTag.src = userInput;

Classic DOM XSS example:

// URL: https://example.com/page#<img src=x onerror=alert(1)>
const msg = location.hash.slice(1);  // Source
document.getElementById("output").innerHTML = msg;  // Sink!

2 Safe DOM APIs and Trusted Types

Replace dangerous sinks with safe alternatives that treat content as text rather than HTML.

Safe text alternatives:

// UNSAFE — parses as HTML
element.innerHTML = userInput;

// SAFE — sets as plain text, no HTML parsing
element.textContent = userInput;

// SAFE — creates a text node
const textNode = document.createTextNode(userInput);
element.appendChild(textNode);

For navigation — validate URL protocols:

function safeNavigate(url) {
  const parsed = new URL(url, window.location.origin);
  if (!["http:", "https:"].includes(parsed.protocol)) {
    throw new Error("Invalid protocol");
  }
  window.location.href = parsed.href;
}

Trusted Types API (modern browsers):

// Set via CSP header: require-trusted-types-for 'script'
const policy = trustedTypes.createPolicy("sanitize-html", {
  createHTML: (html) => DOMPurify.sanitize(html)
});
// Now innerHTML only accepts TrustedHTML objects
element.innerHTML = policy.createHTML(userHtml);

Knowledge Check

0/3 correct
Q1

What makes DOM-based XSS different from reflected XSS?

Q2

Which property safely sets text content without HTML parsing?

Q3

What is the purpose of the Trusted Types API?

Code Exercise

Replace innerHTML with textContent

The search results page uses innerHTML to display the search query, enabling DOM XSS via location.search. Replace the dangerous sink with a safe alternative.

javascript