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 が野放しになっているなら、今すぐこの設定を検討してほしい。泥臭い修正作業かもしれないが、将来のインシデント対応で徹夜することを考えれば、今やるべき最もコスパの良いエンジニアリングだ。
実装でハマったら、また相談してくれ。現場からは以上だ。
コメント