RSTR-XSS-003 — document.write with attacker-controlled DOM data

Summary

document.write (or document.writeln) is called with a string built from window.location.*, document.referrer, document.URL, or similar URL-bar / referrer sources — all directly controllable by an attacker via a crafted link. The HTML is parsed in the page's origin, yielding DOM-based XSS.

Severity

High. Indistinguishable from reflected XSS in impact.

Languages

JavaScript, TypeScript.

What rastray flags

document.write(location.search);              // ← flagged
document.writeln(document.referrer);          // ← flagged
document.write('<h1>' + location.hash + '</h1>');  // ← flagged

What rastray deliberately does not flag

  • document.write(staticString) with a literal argument.
  • document.write(value) where value is computed from a server response — those go through RSTR-XSS-001 / RSTR-XSS-002 paths.
  • document.write of values that were first run through a sanitiser (DOMPurify, escapeHtml). The rule cannot see across calls; if the data was sanitised, suppress per-line.

How to fix it

Stop using document.write entirely. Modern alternatives:

  • For text: element.textContent = ... (safe; no HTML parsing).
  • For attributes: element.setAttribute('href', ...).
  • For controlled markup: element.innerHTML = DOMPurify.sanitize(html).

If you must accept HTML, sanitise it explicitly:

import DOMPurify from 'dompurify';
container.innerHTML = DOMPurify.sanitize(untrusted, { USE_PROFILES: { html: true } });

Note that document.write after the page has loaded silently wipes the entire document — there is no production scenario where it is the right primitive.

References