【実務・中級編】Trusted Types APIによるDOMベースXSSの根本的防御 – アプリケーションセキュリティ & 安全な開発防御ガイド

DOMベースXSSの「最後の砦」:Trusted Typesで危険なシンクを物理的に封じ込める

現場でバリバリとコードを書いている諸君、お疲れ様。
今日はフロントエンド開発における最大の悪夢、「DOMベースXSS」について話そう。

「サニタイズはしてるし、フレームワークも使ってるから大丈夫」。そう思っているなら、一度立ち止まってほしい。脆弱性は常に「開発者が想定しなかったデータの流れ」から生まれる。モダンなReactやVueを使っていても、レガシーなライブラリとの連携や、v-htmlのような「危険な穴」を不用意に使った瞬間、アプリケーションは脆くも崩れ去る。

今日は、ブラウザの力を使って「危険な関数そのものを実行不能にする」、Trusted Typesという強力な防壁について解説する。

—

なぜ従来の対策だけでは不十分なのか

従来のXSS対策の主流は、入力値のバリデーションやエスケープだ。しかし、これらは「開発者が常に完璧なコードを書く」という性善説に依存している。

例えば、以下のコードは典型的なDOMベースXSSのPoCだ。

// 攻撃者: location.hash に細工されたスクリプトを注入
// URL: https://example.com/#
const hash = decodeURIComponent(location.hash.substring(1));
document.getElementById(‘content’).innerHTML = hash; // ここでスクリプトが発火する

innerHTML に渡される前にエスケープを忘れた、あるいはライブラリが内部的に document.write を使っていた。こうした「ヒューマンエラー」をコードレビューだけで根絶するのは不可能に近い。ここで登場するのが Trusted Types だ。

—

Trusted Types:仕組みと核心

Trusted Typesは、ブラウザに対して「特定の関数(シンク)には、信頼されたオブジェクトしか渡させない」と宣言するポリシーだ。

これを有効にすると、innerHTML や location.href に直接文字列(string)を渡そうとした瞬間に、ブラウザが強制的に例外を投げ、実行をブロックする。文字列を渡すには、事前に「Trusted Typeポリシー」を通した「信頼されたオブジェクト」に変換しなければならない。

—

【実務実装】Trusted Typesの導入手順

導入は驚くほどシンプルだ。まずはHTTPレスポンスヘッダーでポリシーを有効化する。

1. HTTPヘッダーによるポリシー適用 (Nginx/Webサーバー設定)

これが最も確実だ。サーバー側で強制的に適用する。

Nginx設定例: レスポンスヘッダーにCSPを追加
add_header Content-Security-Policy “require-trusted-types-for ‘script’;”;

2. JavaScriptでのポリシー定義(コピペ用)

次に、アプリケーション側で「どの文字列なら許可するか」というポリシーを定義する。これがないと、すべてのDOM操作がブロックされてアプリが動かなくなるので注意が必要だ。

// 信頼されたポリシーを作成する(一度だけ実行)
const policy = trustedTypes.createPolicy(‘myAppPolicy’, {
createHTML: (input) => {
// ここでDOMPurifyなどのライブラリを使ってサニタイズを強制する
// 直接文字列を渡すことは許されない
return DOMPurify.sanitize(input);
}
});

// 使用方法
const userContent = ““; // 悪意のある入力
const contentElement = document.getElementById(‘content’);

// 修正前: contentElement.innerHTML = userContent; // -> ブラウザがエラーを投げてブロック!
// 修正後:
contentElement.innerHTML = policy.createHTML(userContent); // 安全なHTMLのみがセットされる

—

現場で陥りやすい罠と対策

Trusted Typesを導入する際、最も苦労するのが「既存のレガシーコードとの共存」だ。

  • 段階的移行: 最初からポリシーを厳しくすると、古いライブラリが軒並み動かなくなる。まずは report-only モードで運用し、どの関数が違反しているかを監視することから始めよう。
  • サードパーティライブラリ: Google Analyticsや広告タグが document.write を使っている場合、それらもブロック対象になる。これらはポリシー内でホワイトリストに追加するか、動的読み込みの手法を見直す必要がある。

レポート用のCSP設定例

Content-Security-Policy: require-trusted-types-for ‘script’; report-to my-reporting-endpoint;

—

チーフエンジニアからの提言

Trusted Typesは「XSSを撲滅する銀の弾丸」ではない。しかし、「脆弱なコードを物理的に実行させない」という強制力は、どんなコーディング規約よりも強力だ。

セキュリティとは、性善説を捨て、システムが壊れることを前提に「多層的な防御」を敷くことにある。Trusted Typesを導入することは、アプリケーションに「防弾チョッキ」を着せるようなものだ。

もし諸君のプロジェクトで未だに innerHTML が野放しになっているなら、今すぐこの設定を検討してほしい。泥臭い修正作業かもしれないが、将来のインシデント対応で徹夜することを考えれば、今やるべき最もコスパの良いエンジニアリングだ。

実装でハマったら、また相談してくれ。現場からは以上だ。

コメント

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