Nicht vertrauenswürdiges HTML bereinigen

Webfunktionen: Sanitizer API

Die sichere Anzeige von nutzergeneriertem HTML ist eine gängige Sicherheitsherausforderung. Die native Sanitizer API bietet eine integrierte, browseroptimierte Möglichkeit, gefährliche Inhalte (wie <script>-Tags oder on*-Attribute) zu entfernen, bevor sie das DOM erreichen.

Die clientseitige Bereinigung schützt die Seite, auf der der Inhalt gerendert wird. Wenn dieselben von Nutzern erstellten Inhalte auch an anderer Stelle gerendert werden (andere Clients, serverseitig gerenderte Seiten oder E‑Mails), sollten Sie sie auch auf dem Server bereinigen, um die Sicherheit zu erhöhen. Achten Sie darauf, dass die Bereinigungsregeln für Server und Client nicht miteinander in Konflikt stehen.

1. HTML mit setHTML() sicher einfügen

Die Sanitizer API wird hauptsächlich über die Methode setHTML() für ein beliebiges Element verwendet. Diese Methode übernimmt das Parsen, die Bereinigung und das Einfügen in einem einzigen Schritt. So wird sichergestellt, dass der Inhalt speziell für den Kontext bereinigt wird, in den er eingefügt wird.

const untrustedHTML = `
  <p>Hello!</p>
  <script>alert('XSS')</script>
  <img src="x" onerror="alert('XSS')">
  <div onclick="doBadThing()">Click me</div>
`;

const container = document.getElementById('output');

// Sanitize and insert in one call
// The default sanitizer removes scripts and event handlers, plus other
// elements and attributes outside its built-in allowlist (such as style)
container.setHTML(untrustedHTML);

2. Benutzerdefinierte Sanitizer-Konfigurationen verwenden

Sie können die Bereinigungsregeln anpassen, indem Sie eine Sanitizer-Instanz erstellen. So können Sie genau festlegen, welche Elemente und Attribute für Ihren spezifischen Anwendungsfall zulässig sind.

Wenn Sie Element.setHTML() und Document.parseHTML() verwenden, werden mit den Standardeinstellungen von Sanitizer alle XSS-unsicheren Elemente und Attribute entfernt. Sie können keine weniger restriktive Sanitizer erstellen, die diese Elemente zulässt, aber Sie können die zulässigen Elemente weiter einschränken.

// Define a restrictive configuration that only allows the specified elements and attributes.
const config = {
  elements: ["p", "b", "i", "strong", "em"],
  attributes: ["class"],
  replaceWithChildrenElements: ['div']
};

const mySanitizer = new Sanitizer(config);

// Apply custom sanitization
container.setHTML(untrustedHTML, { sanitizer: mySanitizer });

Elemente, die nicht in elements enthalten sind, werden zusammen mit ihrem Inhalt entfernt. Listen Sie Wrapper-Elemente, deren Text Sie beibehalten möchten (z. B. div oben), in replaceWithChildrenElements auf. Dadurch wird das Element entfernt, aber die untergeordneten Elemente werden beibehalten.

Wenn Sie einen weniger restriktiven Sanitizer benötigen, der einige XSS-unsichere Elemente oder Attribute zulässt, können Sie einen benutzerdefinierten Sanitizer mit Element.setHTMLUnsafe() und Document.parseHTMLUnsafe() verwenden.

3. Nutzereingaben mit Document.parseHTML() parsen

Wenn Sie nicht vertrauenswürdigen HTML-Code parsen müssen, ohne ihn sofort in das Live-DOM einzufügen, verwenden Sie Document.parseHTML(). Diese Methode gibt ein Document zurück, das gemäß den Standard- oder benutzerdefinierten Regeln bereinigt wurde. Sie können die bereinigten Knoten prüfen oder transformieren (z. B. um eine Vorschau zu erstellen oder Text zu extrahieren), bevor Sie sie einfügen.

Verschieben Sie die resultierenden Knoten auf die Seite, anstatt sie in einen String zu serialisieren und mit innerHTML neu zu parsen, da dies zu einer Mutation-XSS (mXSS) führen kann.

const rawHTML = '<p>Hello <script>console.log("bad")</script></p>';

const doc = Document.parseHTML(rawHTML);
// doc.body now contains the sanitized nodes: <p>Hello </p>
preview.replaceChildren(...doc.body.childNodes);

Fallbacks und Browserunterstützung für die Sanitizer API

Wenn die native Sanitizer API in Ihren Zielbrowsern nicht verfügbar ist, laden Sie eine Bibliothek wie DOMPurify nur dann, wenn die native Unterstützung fehlt. So vermeiden Sie, dass sie an Browser gesendet wird, die sie nicht benötigen. Verwenden Sie in-Prüfungen für die Feature-Erkennung, damit die Prüfung selbst in älteren Browsern funktioniert.

let domPurifyPromise;

function loadDOMPurify() {
  // Code-split with your bundler so DOMPurify is only downloaded when needed
  domPurifyPromise ??= import('dompurify').then((mod) => mod.default ?? mod);
  return domPurifyPromise;
}

export async function safeSetHTML(el, html) {
  const elements = ['p', 'b', 'i', 'strong', 'em'];
  const attributes = ['class'];

  if ('setHTML' in Element.prototype) {
    // setHTML() accepts a plain configuration object as the sanitizer
    el.setHTML(html, { sanitizer: { elements, attributes } });
  } else {
    const DOMPurify = await loadDOMPurify();
    el.innerHTML = DOMPurify.sanitize(html, {
      ALLOWED_TAGS: [...elements, '#text'],
      ALLOWED_ATTR: attributes,
      // Match the native Sanitizer: strip aria-*/data-* and disallowed element content
      ALLOW_ARIA_ATTR: false,
      ALLOW_DATA_ATTR: false,
      KEEP_CONTENT: false
    });
  }
}

Standardmäßig behält DOMPurify die Attribute aria-* und data-* (auch wenn ALLOWED_ATTR angegeben ist) sowie den Textinhalt der entfernten Elemente bei, während der native Sanitizer die Attribute aria-* und data-* entfernt und nicht zulässige Elemente zusammen mit ihrem Inhalt entfernt. Wenn Sie ALLOW_ARIA_ATTR: false, ALLOW_DATA_ATTR: false und KEEP_CONTENT: false festlegen und #text zulassen, entspricht DOMPurify dem nativen Verhalten. DOMPurify hat keine integrierte Option, die replaceWithChildrenElements entspricht. Vermeiden Sie diese Option daher in Konfigurationen, die einen entsprechenden Fallback erfordern.

Wenn auf der Seite vertrauenswürdige Typen (require-trusted-types-for 'script') erzwungen werden, wird beim Zuweisen eines Strings zu innerHTML ein Fehler ausgegeben. setHTML() benötigt keine Trusted Types-Richtlinie, aber der Fallback: Übergeben Sie RETURN_TRUSTED_TYPE: true an DOMPurify.sanitize() und lassen Sie den Richtliniennamen dompurify in Ihrer trusted-types-CSP-Anweisung zu.