【テクニカル・上級編】Trusted Types APIを用いたDOMベースXSSの根本的防御 – アプリケーションセキュリティ & 安全な開発防御ガイド

DOM-based XSSを葬る:Trusted Typesがもたらす「型による強制」のパラダイムシフト

セキュリティの世界で「サニタイズ」という言葉を耳にするたび、私はいつも苦笑いをする。なぜなら、サニタイズは本質的に「穴の空いたバケツをテープで塞ぐ」ような作業だからだ。開発者がどれほど注意深く正規表現を書いても、ブラウザのHTMLパーサーやDOM APIの深淵な挙動――例えば、難読化されたエンティティやブラウザごとのパース差異――を前にすれば、それは無力な防壁と化す。

我々が直面しているDOM-based XSSの根本問題は、「文字列がコードとして実行される境界(シンク)が、アプリケーションのどこにでも存在しすぎる」という設計の欠陥にある。

今日は、この泥沼から抜け出すための最終兵器「Trusted Types API」について、アーキテクトの視点から深掘りする。

—

1. 脆弱性の根源:なぜDOMシンクは制御不能なのか

DOM-based XSSの多くは、innerHTMLやdocument.write()といった「危険なシンク」に、外部からの信頼できないデータがそのまま流し込まれることで発生する。

従来の防御策は、入力値を検証する「フィルタリング」だった。しかし、これは「何を許可すべきか」ではなく「何を禁止すべきか」というブラックリスト思考に陥りがちだ。攻撃者は常にこのブラックリストの裏をかく。メモリレベルでパース挙動を操作し、JavaScriptの実行コンテキストを奪取する。

Trusted Typesは、このパラダイムを「文字列を型(Type)でラップし、信頼された型以外はシンクへの代入をブラウザレベルで拒絶する」という強制的な制約へと書き換える。

2. Trusted Typesのアーキテクチャ:ガードレイルの敷設

Trusted Typesを有効にすると、innerHTMLなどのシンクは、生の文字列を受け付けなくなる。代わりに、開発者が定義した「ポリシー」を通過したTrustedHTMLオブジェクトのみが許可される。

ポリシーの実装例

まずは、アプリケーションの初期化フェーズでポリシーを定義する。

// ポリシーの定義:ここで安全な変換ロジックを強制する
const policy = trustedTypes.createPolicy(‘default’, {
createHTML: (input) => {
// DOMPurifyなどの信頼できるライブラリを通すことを強制する
// ここで許可されない文字列は、ブラウザ側で実行が阻止される
return DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: true });
},
createScriptURL: (url) => {
// 外部スクリプトの読み込み元をホワイトリストで厳格に管理する
const allowed = [“https://trusted.cdn.com/”];
if (allowed.includes(url)) return url;
throw new Error(“信頼されていないURLです”);
}
});

この実装が強力なのは、「開発者がサニタイズを忘れた場合、ブラウザが例外を投げて実行を停止する」という点にある。バグが「実行時エラー」として顕在化するため、脆弱性を抱えたまま本番環境へデプロイされるリスクを物理的に遮断できるのだ。

3. CSPによる強制適用のフェーズ

コードレベルでの対応が完了したら、HTTPヘッダーでこのポリシーを強制する。CSP(Content Security Policy)に以下を追加する。

Content-Security-Policy: require-trusted-types-for ‘script’; trusted-types default;

この設定により、innerHTMLへの直接代入は、開発環境であれ本番環境であれ、例外なくブロックされる。これは、レガシーなコードベースを一掃するための「アーキテクチャ上の強制力」として機能する。

4. 監査とインシデントハンドリングの観点から

セキュリティアーキテクトとして言っておきたいのは、Trusted Typesは「魔法の杖」ではないということだ。

  • ポリシーの脆弱性: createHTML内で不適切なサニタイズを行えば、当然XSSは発生する。ポリシーそのもののコードレビューは、公開鍵暗号の設計と同等に厳格に行うべきだ。
  • レガシーの負債: 大規模な既存アプリケーションに導入する場合、全てを一度にTrusted Types化するのは不可能だ。Reporting APIを併用し、違反ログを収集・分析し、影響範囲を特定するフェーズを必ず設けること。

// 違反レポートの収集設定
document.addEventListener(“securitypolicyviolation”, (e) => {
console.error(“Trusted Types 違反検知:”, e.violatedDirective, e.blockedURI);
// 収集したログをバックエンドへ送り、攻撃の兆候や未修正箇所を特定する
});

結びに代えて:防御の「型」を作る

サイバー攻撃者は常に、境界の曖昧さを突いてくる。通信パケットの構造や、メモリ管理の隙間、そして我々エンジニアの「うっかり」というヒューマンエラー。

Trusted Typesは、それら「曖昧な境界」を「厳格な型定義」へと変換する試みだ。これは単なるコードの修正ではなく、開発組織における「安全な状態」の定義を、ブラウザという強力なOSレベルのプレイヤーに委譲する戦術である。

もし君が大規模なWebアプリケーションのセキュリティを預かっているのなら、今すぐCSPを書き換え、Trusted Typesの導入を開始してほしい。それが、DOM-based XSSという時代遅れの脆弱性を、現代のWebスタックから物理的に消滅させるための最短ルートだ。

泥臭いログ解析も重要だが、まずは「バグを発生させない構造」を構築する。それが、最高峰の防衛技術というものだ。

コメント

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