DOMベースXSSの「最後の砦」:Trusted Typesで脆弱性を物理的に抹殺する
現場のエンジニア諸君、お疲れ様。今日もどこかのWebアプリで、誰かが「JavaScriptの文字列結合」という甘い罠に足を取られている。
クロスサイトスクリプティング(XSS)といえば、長年「入力値のサニタイズ(無害化)」や「出力時のエスケープ」がセオリーとされてきた。だが、現代のクライアントサイド・レンダリング主体のアプリにおいて、DOMベースのXSSは、もはや古典的なエスケープ処理だけで防ぎきれるものではない。JavaScriptの実行フローが複雑化し、ライブラリの裏側で何が起きているか追いきれないからだ。
そこで今回は、ブラウザが提供する最強の防壁「Trusted Types API」について解説する。これを使えば、脆弱なシンク(危険な関数)へ「ただの文字列」を渡すこと自体を、ブラウザレベルで物理的に遮断できる。
—
1. なぜ「エスケープ」だけでは勝てないのか
DOMベースXSSの恐ろしさは、サーバーを一切経由せず、ブラウザ内のJavaScript処理だけで完結することにある。
例えば、よくある脆弱なコードがこれだ。
// 攻撃者がURLパラメータに ?name= を仕込むと…
const name = new URLSearchParams(window.location.search).get(‘name’);
// ここで危険なシンクである innerHTML に、悪意のあるスクリプトが挿入される
document.getElementById(‘content’).innerHTML = name;
開発者は「まあ、このデータは社内ツールだし」とか「ライブラリがよしなにやってくれるだろう」とタカをくくる。しかし、攻撃者はその「よしなに」の裏にあるシンク(innerHTML, outerHTML, document.write, eval等)をピンポイントで狙ってくる。
Trusted Typesは、「シンクに渡せるのは、特定のルールをクリアした『信頼されたオブジェクト』のみ」という制約を強制する。これを通さない文字列は、ブラウザが実行を拒否する。まさに「ゼロトラスト」のフロントエンド版だ。
—
2. Trusted Typesの実装:コピペで使える「政策(Policy)」
Trusted Typesを導入するには、まず「Policy」を定義し、文字列をオブジェクトにラップするルールを作る必要がある。以下のコードは、実務でそのまま使える雛形だ。
// 1. セキュリティポリシーを定義する
// 信頼されたHTMLのみを許可するポリシーを生成
const policy = window.trustedTypes.createPolicy(‘mySecurityPolicy’, {
createHTML: (input) => {
// ここでDOMPurifyなどのライブラリを使い、サニタイズを強制する
// これにより、ポリシーを通らない文字列は全てここで弾かれる
return DOMPurify.sanitize(input);
}
});
// 2. 実装側:直接文字列を渡さず、ポリシーを通したオブジェクトを渡す
const untrustedInput = new URLSearchParams(window.location.search).get(‘name’);
const contentElement = document.getElementById(‘content’);
// 修正後:innerHTMLに直接文字列を代入するとブラウザがエラーを吐いて停止する
// 代わりにポリシーを通したオブジェクトを代入する
contentElement.innerHTML = policy.createHTML(untrustedInput);
3. 本番環境への適用:HTTPレスポンスヘッダーによる強制
コードを書くだけでは不十分だ。開発者がサボって「脆弱なコード」を書いてしまった場合でも、ブラウザに強制させなければ意味がない。サーバー側で以下のHTTPレスポンスヘッダーを送信し、ポリシーの適用を強制する。
Nginxの設定例:
全てのページでTrusted Typesを有効化し、ポリシー違反はレポートする
add_header Content-Security-Policy “require-trusted-types-for ‘script’; report-uri /csp-violation-report-endpoint;”;
これを設定した瞬間、アプリ内の全てのinnerHTMLへの直接代入は、ブラウザのコンソールで「TypeError」を引き起こし、スクリプトの実行がブロックされる。「修正漏れがあっても攻撃は成立しない」という最強の防御ラインだ。
—
4. 現場のチーフとしてのアドバイス
Trusted Typesの導入は、最初は既存コードの修正で悲鳴が上がるかもしれない。特に古いレガシーなライブラリを使っている場合、あちこちでエラーが出るはずだ。
しかし、考えてみてほしい。そのエラーこそが、これまで見過ごされていた「脆弱性の芽」そのものなのだ。
1. 段階導入: 最初は report-only モードで運用し、レポートを収集してどこが危険なシンクを使っているか特定する。
2. 型安全の徹底: createHTML ポリシーの中で、DOMPurify を使うのは必須のルーティンだ。自前で正規表現を書くような泥臭いことはせず、枯れたライブラリに頼るのがプロの作法だ。
3. チームへの啓蒙: 「なぜこれが必要なのか」を語るときは、「脆弱性があるから」と言うのではなく、「ブラウザに実行権限を渡すとき、我々が厳格な検問所を設けるためだ」と伝えよう。
技術は日々進化する。エスケープ処理に頼り切る時代は終わりだ。ブラウザの機能をフル活用し、堅牢なWebアプリケーションを構築してほしい。
何かあればいつでも相談してくれ。健闘を祈る。
コメント