DOM型XSSを「過去の遺物」にする:Trusted Types API完全攻略ガイド
現場でインシデント対応をしていると、いまだに「サニタイズ漏れ」という言葉を耳にする。だが、正直に言おう。人間が手動でサニタイズを行う時代はもう終わったんだ。
DOM型XSS(DOM-based XSS)は、サーバーサイドを通さず、クライアントサイドのJavaScriptだけで完結する攻撃だ。現代のSPA(Single Page Application)において、これは「コードのどこかに一つでも脆弱なシンク(危険な関数)があればアウト」という、極めてシビアな状況を作り出している。
今日は、そんな「モグラ叩き」のような脆弱性修正から君たちを解放する、Trusted Types APIという最強の切り札について解説する。
—
1. なぜ「サニタイズ」では不十分なのか
まず、敵を知ろう。DOM型XSSのPoC(概念実証)は驚くほど簡単だ。例えば、ユーザーの入力値をそのまま innerHTML に放り込んでいるアプリがあるとしよう。
// 攻撃者がURLパラメータに ?name= を仕込むと…
const params = new URLSearchParams(window.location.search);
const name = params.get(“name”);
// ここでDOMシンクが発火する
document.getElementById(“user-profile”).innerHTML = “ようこそ、” + name + “さん”;
開発者はここで「エスケープ関数を作ればいい」と考える。だが、複雑なフロントエンドコードの中で、すべての箇所に正しく適用するのは不可能に近い。「一度でもミスをすれば終わり」という設計自体が、セキュリティの敗北なんだ。
—
2. Trusted Types:ブラウザによる「強制的な型安全」
Trusted Typesは、DOMシンク(innerHTML, outerHTML, document.write 等)に対して、「ただの文字列」を渡すことを禁止する仕組みだ。
これを有効にすると、JavaScriptエンジンは「許可されたオブジェクト(Trusted Type)」以外が渡された瞬間にエラーを投げるようになる。つまり、開発段階で「型の不一致」としてバグを叩き潰せるわけだ。
導入ステップ:まずはポリシーを定義する
まずは、信頼できる変換ルール(Policy)を定義する。ここが唯一の「安全な場所」になる。
// セキュリティポリシーの作成
if (window.trustedTypes && window.trustedTypes.createPolicy) {
const policy = window.trustedTypes.createPolicy(‘myAppPolicy’, {
// 文字列を安全なHTMLオブジェクトに変換する関数
createHTML: (input) => {
// ここでDOMPurifyなどの信頼できるライブラリを通すのが鉄則
return DOMPurify.sanitize(input);
}
});
// 以後、innerHTML等にはこのpolicyを通したオブジェクトしか渡せない
const userContent = policy.createHTML(““);
document.getElementById(“profile”).innerHTML = userContent; // OK
// document.getElementById(“profile”).innerHTML = “…“; // ここでブラウザが例外を投げる!
}
—
3. Webアプリ全体を「強制」する(CSPの設定)
コードを書くだけでは甘い。もし誰かがポリシーを定義し忘れたら? そのために、HTTPレスポンスヘッダーで「Trusted Typesの使用を強制」する。
NginxやWebサーバーの設定ファイルに以下のCSP(Content Security Policy)を追加してほしい。
Nginxの設定例
add_header Content-Security-Policy “require-trusted-types-for ‘script’; trusted-types myAppPolicy;”;
require-trusted-types-for 'script':すべてのDOMシンクにTrusted Typesを強制する。trusted-types myAppPolicy:許可するポリシー名を指定する。
これを設定した瞬間、君のアプリで「ポリシーを通していない文字列」を innerHTML に代入しようとすると、ブラウザは即座に実行をブロックし、コンソールに強力なエラーを表示する。これこそが、セキュリティの「防御的設計」だ。
—
4. 現場のエンジニアへ:明日からのアクションプラン
Trusted Typesを導入する際は、いきなり厳格に適用すると既存機能が全滅する。以下の手順で進めるのが、インシデントを発生させないプロのやり方だ。
1. レポートモードで試す:
Content-Security-Policy-Report-Only を使い、ブロックはせずにエラー箇所をログに流し続ける。これで「どこが脆弱か」の全貌が見える。
2. DOMPurifyの活用:
自作のサニタイズ関数など使うな。世界中の知見が詰まった DOMPurify をポリシー内に組み込むこと。
3. 段階的な適用:
まずは新規機能から導入し、古いモジュールはリファクタリングの過程でポリシーの対象にしていく。
最後に
セキュリティとは、「攻撃を防ぐこと」ではない。「脆弱性を生み出せない構造を作ること」だ。
Trusted Typesは、君たちのコードをより堅牢で、かつ「なぜ安全なのか」が説明できるものに変えてくれる。明日、チームのコードベースにこの仕組みを一行加えるだけで、将来の何千時間もの修正工数と、顧客からの信頼喪失という最悪の事態を防げるかもしれない。
さあ、退屈なサニタイズコードは捨てて、型安全な世界へ移行しよう。質問があれば、いつでも聞かせてくれ。
コメント