flawopen.com/XSS in JavaScript/DOMPurify and innerHTML
DOMPurifyだけで innerHTML は安全になりますか?
DOMPurifyは、出力を後から変更せずHTML sinkへ直接渡す場合、リッチHTML向けの強力な許可リスト型サニタイザーです。荷受け口で荷物を検査するようなものです。後で別の工程が詰め直したり別の入口から入れたりすれば、最初の検査は届きません。通常のテキストには textContent を使い、HTMLとして解析しないでください。
// 古いバージョン/ポリシーで保存時にサニタイズ。 saveToDb(DOMPurify.sanitize(input)); // 後のコードは検証せず保存文字列を信頼する。 el.innerHTML = fetchedFromDb;
// リッチ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 と組み合わせないでください。
本番チェックリスト
- ✓通常のテキストには textContent を使います。制約したリッチHTMLが必要な場合だけDOMPurifyを使います。
- ✓最終HTML sinkで最新DOMPurifyとレビュー済みの最小許可リストを使ってサニタイズし、その後は結果を変更しません。
- ✓出力はHTMLコンテキスト内で使います。JavaScript、CSS、属性、XML、SVGなど別の解析コンテキストに流用しません。
- ✓永続設定の setConfig()、hooks、ADD_TAGS/ADD_ATTR、サーバー側DOMの版、Trusted Typesのポリシー/CSPをまとめて確認します。
HTML sinkを検索する
rg -n -g '*.{js,ts,jsx,tsx}' '(innerHTML|outerHTML|insertAdjacentHTML|document\.write|dangerouslySetInnerHTML)' .