FlawOpen

flawopen.com/XSS in JavaScript/DOMPurify and innerHTML

DOMPurifyだけで innerHTML は安全になりますか?

リファレンス
要点

DOMPurifyは、出力を後から変更せずHTML sinkへ直接渡す場合、リッチHTML向けの強力な許可リスト型サニタイザーです。荷受け口で荷物を検査するようなものです。後で別の工程が詰め直したり別の入口から入れたりすれば、最初の検査は届きません。通常のテキストには textContent を使い、HTMLとして解析しないでください。

危険 — 古いサニタイズ済みデータをsinkへ渡す
// 古いバージョン/ポリシーで保存時にサニタイズ。
saveToDb(DOMPurify.sanitize(input));

// 後のコードは検証せず保存文字列を信頼する。
el.innerHTML = fetchedFromDb;
より安全 — HTML sink向けにサニタイズ
// リッチHTMLが必要なら最新のライブラリで
// HTMLコンテキストに挿入する直前に処理する。
el.innerHTML = DOMPurify.sanitize(untrustedHtml, {
  USE_PROFILES: { html: true },
});

// 通常のテキストはHTMLとして解析しない。
textNode.textContent = untrustedText;

保存時に一度だけサニタイズする方法が弱い理由

保存時のサニタイズ自体が常に危険なわけではありませんが、古いデータ境界を作ります。サニタイザーの修正、ポリシー変更、処理を通らない経路があっても、後の描画処理が古い出力を信頼する可能性があります。描画時に現在のバージョンとsink向けポリシーで再度サニタイズしてください。挿入前に結果を変更したり別ライブラリに渡したりしないでください。後処理で効果が失われることがあります。

設定と出力コンテキストが重要

DOMPurifyの既定設定はHTML、SVG、MathMLを許可します。HTMLだけでよい場合は USE_PROFILES: { html: true } で範囲を絞ります。レビューせずタグや属性を許可しないでください。DOMPurifyはCSSをサニタイズせず、外部リソース要求もすべて防ぎません。出力はHTML内だけで使い、JavaScript、属性、XML、SVG、CSSに流用しないでください。

Trusted Typesとサーバー側描画

対応環境ではContent Security Policyで Trusted Types を強制し、承認ポリシーを通らない文字列のHTML sinkへの代入を拒否します。DOMPurifyは TrustedHTML を返せますが、危険な設定や後処理までは修正しません。サーバー側ではDOM実装に依存するため、jsdomを更新し、セキュリティ用途のサニタイズで happy-dom と組み合わせないでください。

本番チェックリスト

HTML sinkを検索する

rg -n -g '*.{js,ts,jsx,tsx}' '(innerHTML|outerHTML|insertAdjacentHTML|document\.write|dangerouslySetInnerHTML)' .
各結果で信頼できないデータからsinkまで追跡し、挿入直前にサニタイズされることと、後続コードがマークアップを変更しないことを確認します。

参考資料

関連項目 クロスサイトスクリプティング(XSS、CWE-79)の仕組みと各言語での防ぎ方 — flawopen.com Markdown のレンダリングは XSS に安全? SvelteはデフォルトでXSSを防げる? SVG アップロードは XSS のリスクになりますか? Vue v-htmlのXSSリスクと安全な使い方 WKWebView loadHTMLString は未信頼コンテンツに安全? 反射型・格納型・DOM ベース XSS の違いは?