【テクニカル・上級編】Sanitizationライブラリを用いたHTML入力の無害化 – アプリケーションセキュリティ & 安全な開発防御ガイド

DOMPurifyは「魔法の杖」ではない:HTML Sanitizationの深淵とアーキテクチャの防衛線

セキュリティ・アーキテクト諸君、現場での「とりあえずDOMPurifyを通せば安全」という思考停止に、私は日々溜息をついている。

確かにDOMPurifyは優れたライブラリだ。しかし、彼らが提供するのは「攻撃を完璧に遮断する障壁」ではなく、「ブラウザのパース挙動を前提とした、制御されたフィルター」に過ぎない。XSS(クロスサイトスクリプティング)は、単なる文字列の置換問題ではなく、ブラウザのHTMLパーサー、DOMツリーの生成プロセス、そして現代のブラウザが持つ脆弱性そのものを舞台にした、低レイヤの攻防なのだ。

今日は、表面的なライブラリ導入論を捨て、インジェクション攻撃の本質と、我々が実装すべき堅牢なガードレイルについて語ろう。

1. HTMLパーサーの「非決定性」という脆弱性

攻撃者は常に、サーバーサイドのSanitizerとクライアントのブラウザ間における「解釈のズレ」を狙っている。

例えば、DOMPurifyはW3Cの仕様に基づきタグをサニタイズするが、ブラウザによっては「仕様外の奇妙なパース」を行うことがある。過去のCVEを振り返れば、サニタイズ後のHTMLが特定のブラウザで実行される際に、予期せぬDOMノードとして再解釈され、スクリプトが発火した事例は枚挙に暇がない。

これは、パケット構造の解析において「正規化の不一致」がIDS/IPSの回避を許すのと全く同じメカニズムだ。防御側が「安全だ」と判断した構造が、実行環境(ブラウザ)に到達した瞬間に攻撃コードへ変貌する。この「解釈の二重性」を封じ込めるには、サニタイズを単なるライブラリ任せにするのではなく、多層防御のアーキテクチャで囲い込む必要がある。

2. ホワイトリスト方式の「攻めの設計」

DOMPurifyを導入する際の鉄則は、ホワイトリストの徹底的な絞り込みだ。多くの開発者は「利便性」を優先し、style属性やdata-属性を安易に許可してしまう。これが命取りになる。

import DOMPurify from ‘dompurify’;

/

  • 堅牢性を最優先したサニタイズ設定
  • 許可するプロパティは最小限に絞り、CSSや複雑な属性は原則拒否する

/
const clean = DOMPurify.sanitize(dirtyInput, {
ALLOWED_TAGS: [‘b’, ‘i’, ‘em’, ‘strong’, ‘a’], // 許可するタグを限定
ALLOWED_ATTR: [‘href’], // hrefのみ許可
ALLOW_DATA_ATTR: false, // data属性はXSSの温床になるため原則無効
KEEP_CONTENT: false, // 不正なタグの中身も削除する(重要)
USE_PROFILES: { html: true }, // プロファイル設定で攻撃ベクトルを制限
});

ここでのポイントは、KEEP_CONTENT: falseの設定だ。悪意あるタグ自体を削除しても、その中身のテキストが残れば、後続のJavaScriptの処理で予期せぬinnerHTMLへの挿入が発生し、DOMベースのXSSを誘発することがある。「疑わしきは全消去」、これがインシデントハンドリングの教訓だ。

3. 生成AI時代の「プロンプト・インジェクション」への応用

最近のアーキテクチャで無視できないのが、生成AI(LLM)への入力としてのHTMLだ。もしあなたのシステムがAIエージェントにHTMLを渡す構成なら、DOMPurifyだけでは不十分だ。LLMはHTMLの構造そのものを「指示」として解釈する。

これに対するガードレイルとして、私は以下のステップを提唱している。

1. 静的構造の正規化: HTMLを一旦DOMツリー化し、属性を完全に正規化して再シリアライズする。
2. セマンティック・フィルター: 文法的な正当性だけでなく、LLMが「命令」として解釈しそうな不自然な構造(巨大な隠しテキストや、特定のスクリプト実行を促す隠しリンク)を検知して遮断する。
3. トークン制限と出力監査: LLMからの出力が、入力された汚染データに依存していないか、入力と出力の依存関係をトレーサビリティとして監視する。

4. 最後に:セキュリティは「状態」ではなく「プロセス」である

パッチを当て、ライブラリをアップデートし、設定をチューニングしても、明日には未知のブラウザ仕様によるCVEが報告されるかもしれない。

真のセキュリティ・アーキテクトは、ライブラリを過信しない。データが入力され、処理され、出力されるまでの全過程を「信頼できないパス」として扱い、ネットワークレベルでのWAF、アプリケーションレベルでのサニタイズ、そしてブラウザレベルでのCSP(Content Security Policy)という、三段構えの防衛線を構築せよ。

特にCSPのscript-src 'self'は必須だ。サニタイズをすり抜けたコードがあったとしても、CSPが外部からの悪意あるスクリプト読み込みを封じれば、攻撃者は「実行権限の奪取」という最終目的を達成できない。

技術は常に進化する。だが、攻撃者の狙いは常に「最も脆弱な境界」だ。諸君、まずは自身のシステムのCSP設定を見直すところから始めてみてはどうだろうか。それが、泥臭いインシデントハンドリングから導き出した、私の答えだ。

コメント

タイトルとURLをコピーしました