「その文字列、本当に信じていいの?」DOMベースXSSと最強の門番「Trusted Types」の話
こんにちは。セキュリティの現場で日々、泥臭いインシデントと戦っているエンジニアです。
今日は、Web開発の現場で避けては通れない「XSS(クロスサイトスクリプティング)」、特にDOMベースXSSという少し厄介な攻撃と、それを根本から封じ込めるブラウザの最新機能「Trusted Types」についてお話しします。
専門用語が並ぶと頭が痛くなりますよね。まずは、身近な例えから入っていきましょう。
—
1. DOMベースXSSって、つまりどういうこと?
想像してみてください。あなたは自分の家の玄関(Webブラウザ)に立っています。あなたは「宅配便です」というメモ(外部からの入力データ)を受け取って、中身を確かめもせずに玄関のドアを全開にしてしまいました。
もし、そのメモが宅配業者ではなく、悪意ある泥棒からのものだったらどうでしょう? 「ドアを全開にせよ」という命令が書かれていたら、泥棒はあなたの家の中に堂々と侵入できてしまいますよね。
これがDOMベースXSSです。
- 攻撃の仕組み: WebサイトがURLのパラメータやローカルストレージなどの「外部から来た怪しい文字列」を、検証なしで
innerHTMLやdocument.writeといった「危険な命令(シンク)」に直接渡してしまうことで発生します。 - 何が怖いの?: 攻撃者はあなたのサイト上で勝手にJavaScriptを実行し、ユーザーのセッションクッキーを盗んだり、偽の入力フォームを表示してパスワードを奪ったりします。
「自分は気をつけて実装しているから大丈夫」と思っても、複雑なフロントエンドフレームワークを使っていると、どこでデータが加工され、どこで危険な命令に渡されるかを見失うことがよくあります。
—
2. Trusted Types:ブラウザという「凄腕の門番」を雇う
そこで登場するのがTrusted Types APIです。
これまでのセキュリティ対策(サニタイズなど)は、いわば「泥棒を見分けるための研修」のようなものでした。人間がやる以上、どうしてもミスが起きます。
一方、Trusted Typesは「指紋照合システム付きの自動ドア」です。
ブラウザに対して「この関数を通っていない文字列は、どんな命令であっても絶対に実行するな!」と命令しておくのです。もし、型が正しくない(=信頼されていない)文字列が危険な命令に渡されようとすると、ブラウザが即座にブロックしてくれます。
—
3. 実装のステップ:まずは門番を配置しよう
Trusted Typesを有効にするには、サーバーから以下のHTTPレスポンスヘッダーを送信するだけです。これだけで、ブラウザは「怪しい文字列」をすべて拒否し始めます。
サーバーからのレスポンスヘッダーで「門番」を起動
Content-Security-Policy: require-trusted-types-for ‘script’;
これだけで、element.innerHTML = "ユーザー入力" のようなコードはブラウザにブロックされ、実行されなくなります。
ポリシーを作成して「安全な文字列」を許可する
しかし、これだけだと自分のサイト内の動的な処理すら動かなくなってしまいますよね。そこで、「このルールを通った文字列ならOK」というポリシーを作成します。
// 信頼できる型を作成するポリシーを定義
const policy = trustedTypes.createPolicy(‘myPolicy’, {
createHTML: (input) => {
// ここで安全な処理(サニタイズなど)を行う
// 例:DOMPurifyライブラリを使うのが鉄板です
return DOMPurify.sanitize(input);
}
});
// 使用時は、生の文字列ではなく「ポリシーを通した型」を使う
const userInput = ““;
element.innerHTML = policy.createHTML(userInput); // これなら安全!
もし、ポリシーを無視して生の文字列を渡そうとすると、ブラウザが「信頼されていないデータです!」とコンソールにエラーを吐いて止めてくれます。これぞまさに、セキュリティの「水際対策」ですね。
—
4. なぜこれが「根本的」な解決策なのか
従来のセキュリティ対策は「攻撃パターンを検知する」という後手に回ったものが主流でした。しかし、Trusted Typesは「データの型そのものを管理する」というアプローチです。
- 開発者のミスを強制的に防ぐ: 「あとでサニタイズすればいいや」という甘えを許しません。
- ブラウザネイティブの保護: JavaScriptライブラリの脆弱性に依存せず、ブラウザ自体が守ってくれます。
- 可視化: どこで「信頼できないデータ」が使われそうになったかがコンソールで一目瞭然になるため、デバッグも非常に楽になります。
—
最後に:一歩ずつ、確実に。
新しい技術を導入するのは勇気がいりますよね。「今のサイトが動かなくなったらどうしよう」と不安になるのは当然です。
まずは、Content-Security-Policy-Report-Only ヘッダーを使って、「ブロックはせずに、違反があったらログだけ飛ばす」という設定から始めてみてください。どれくらいのコードが現在「危険な状態」にあるのか、まずは現状を知ることからセキュリティ対策は始まります。
セキュリティは、一度の完璧な設定よりも、「継続して守り続ける仕組み」を作ることが大切です。今日から、その「門番」をあなたのアプリケーションにも雇ってみませんか?
何か分からないことがあれば、いつでも相談してくださいね。一歩ずつ、一緒に強固なWebを作っていきましょう!
コメント