DOM-based XSSの深淵:DOMPurifyによる「フロントエンドの要塞化」とアーキテクチャの真実
セキュリティの現場で「XSSは終わった問題」と嘯く者がいるならば、その者は間違いなく過去5年間のモダンWebアプリの脆弱性レポートを読んでいない。反射型や格納型がサーバーサイドのテンプレートエンジンで抑制されつつある一方で、現代のフロントエンド開発は、APIから流し込まれるJSONを動的にDOMへと書き換える「DOM-based XSS」の温床となっている。
特にシングルページアプリケーション(SPA)において、innerHTMLやouterHTMLへの盲目的な信頼は、即座にRCE(リモートコード実行)と同等の脆弱性に直結する。今回は、この泥沼の戦場において、信頼のアンカーとなる「DOMPurify」の極致的な実装と、アーキテクチャ設計における防衛論を紐解く。
なぜ「ブラックリスト」は死滅したのか
かつて我々は、悪意ある文字列を排除するための「ブラックリスト方式」に固執していた。しかし、これは「未知の未知(Unknown Unknowns)」に対する防衛としては、あまりに無力だ。javascript:プロトコルの難読化、data:スキームによるBASE64エンコード、そしてブラウザのパーサーが持つ「仕様の曖昧さ」を突いたペイロードは、正規表現によるフィルタリングをいとも簡単にすり抜ける。
DOMPurifyが業界標準たる所以は、完全なホワイトリスト方式を採用している点にある。これは、許可されたタグと属性以外の全てを容赦なく「無害化」する設計だ。ブラウザのDOMパーサーを活用し、実際にツリー構造を構築した上で、許可リストに合致しないノードを即座に削除する。この「パーサーを逆利用する」というアプローチこそが、低レイヤの仕様欠陥に対する最も理にかなった防衛策なのだ。
DOMPurifyの堅牢な実装と設定の極意
単にライブラリを入れるだけでは不十分だ。攻撃者はAPIのレスポンスに潜み、想定外の属性やイベントハンドラでJSコンテキストの奪取を試みる。以下は、セキュリティアーキテクトが考慮すべき「防衛的設定」のサンプルである。
import DOMPurify from ‘dompurify’;
/
- 厳格なホワイトリストベースのサニタイズ設定
- セキュリティの観点から、必要最小限の機能のみを許可する
/
const sanitizeConfig = {
// 許可するタグの限定(必要以上に許可しない)
ALLOWED_TAGS: [‘b’, ‘i’, ‘em’, ‘strong’, ‘a’, ‘p’],
// 許可する属性の限定(onイベントハンドラは絶対禁止)
ALLOWED_ATTR: [‘href’, ‘title’, ‘target’],
// 外部リソースの埋め込みを制御(DOM-based XSSの入り口を塞ぐ)
ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto|ftp):|[^:/?#](?:[/?#]|$))/i,
// 危険なプロトコルを除外するための設定(javascript:などはここで弾く)
ADD_DATA_URI_TAGS: [],
// 戻り値を文字列ではなく、信頼できるDOMオブジェクトとして扱う(DOM Clobbering対策)
RETURN_DOM: false,
// 警告などをコンソールに出力させる(監査用)
RETURN_DOM_FRAGMENT: false,
};
// 実装例:信頼できない外部データ(APIレスポンス等)をDOMへ挿入する直前に実行
const untrustedInput = fetchFromExternalAPI();
const cleanHTML = DOMPurify.sanitize(untrustedInput, sanitizeConfig);
document.getElementById(‘content-area’).innerHTML = cleanHTML;
アーキテクチャの盲点:DOM Clobbering
DOMPurifyを使用する際、必ず考慮すべき脅威がDOM Clobbering(DOMの破壊)だ。攻撃者が特定のID名を持つHTML要素を挿入することで、グローバルなwindowオブジェクトのプロパティを上書きし、アプリケーションのロジックを乗っ取ることがある。
RETURN_DOM: trueを選択する場合、DOMPurifyは生成されたノードを返すが、それがアプリケーションの実行コンテキストにどのように影響を与えるかを静的解析する必要がある。もし可能であれば、DOMPurify.addHook('uponSanitizeElement', ...)を用いて、特定のIDや名前がグローバル汚染を誘発しないか、実行時にチェックを入れるレイヤーを一枚噛ませるのがプロの流儀だ。
未来への視座:生成AIとプロンプトインジェクションへの応用
今、我々が直面しているのは、単なるXSSだけではない。LLM(大規模言語モデル)の出力結果をWeb画面に表示する際、その出力自体が「プロンプトインジェクション」を内包しているケースだ。
将来的な防衛設計としては、DOMPurifyのようなサニタイザーを、LLMの出力に対する「ガードレイル」として機能させるアプローチが有効である。AIが生成したマークダウンやHTMLを、そのままブラウザに流すのではなく、DOMPurifyを通して「実行可能なスクリプトを一切含まない純粋なコンテンツ」へと強制変換する。
最後に:防御は「信頼」の管理である
セキュリティにおける敗北とは、システムが突破されたことではなく、システムが「何を信頼してはいけないか」という境界線を曖昧にしたときに発生する。
DOMPurifyを単なるツールとしてではなく、「外部からの入力を、自らが制御可能な安全圏へと引きずり込むためのゲートウェイ」として定義せよ。技術的な実装はコードに宿るが、真の防衛は「全ての入力は悪意あるものである」という、冷徹なまでのエンジニアリング・パラノイアから始まるのだ。
現場のエンジニア諸君、ブラウザのパーサーという名の深淵を覗き込む覚悟があるか。その一歩が、貴社のサービスを、そしてユーザーを救うことになる。
コメント