こんにちは。セキュリティの世界へようこそ。
現場で泥臭いインシデント対応をしていると、「サーバー側で対策したはずなのに、なぜか攻撃が止まらない」という相談をよく受けます。その犯人の多くが、今回お話しする「DOM型XSS」です。
教科書には難しいことが書いてありますが、要は「サーバーに届かない場所で起きる、巧妙な空き巣事件」なんです。今日は、新人エンジニアの皆さんと一緒に、この厄介な「見えない脅威」を紐解いていきましょう。
—
1. DOM型XSSってなに?「家の鍵」で例えると
まず、一般的なXSS(反射型や格納型)は、サーバーに悪意のあるメッセージを送り込み、それが画面に表示されることで被害が出ます。これは例えるなら、「手紙に毒を塗って、郵便局(サーバー)を経由して相手に届ける」ようなもの。郵便局で検閲(バリデーション)すれば防げますよね。
一方で、DOM型XSSは違います。
これは、「家主(ブラウザ)が、玄関先で拾った怪しいメモを、家の中(DOM)に持ち込んで、そのまま読み上げてしまう」ようなものです。
サーバーは全く関与しません。ブラウザの中だけで、JavaScriptがURLのパラメータ(#以降など)を拾い上げ、それを「危険なシンク(実行場所)」に放り込んでしまう。これがDOM型XSSの正体です。
—
2. なぜ危ないの?「危険なシンク」のメカニズム
開発者が良かれと思って書いたJavaScriptが、実は泥棒の味方をしていることがあります。
例えば、URLのフラグメント(#以降)を使って、ページ内の表示を変える機能を作ったとしましょう。
// 攻撃の入り口:URLの #name=太郎 を読み取る
const fragment = window.location.hash.substring(6);
// 危険なシンク:DOMへ直接書き込む
// これが「泥棒を家に入れる」行為です!
document.getElementById(‘welcome’).innerHTML = “こんにちは、” + fragment + “さん!”;
もし攻撃者が https://example.com/#name= というURLを誰かに送ったらどうなるでしょう?
innerHTML は、渡された文字列を「HTML」として解釈します。結果、onerror 属性が発火し、意図しないJavaScriptが実行されてしまうのです。これがDOM型XSSの基本的な実行フローです。
—
3. 「防犯」の極意:こうやって対策しよう
では、どうすればこの「空き巣」を防げるのでしょうか。対策は大きく分けて二つあります。
その①:危険な命令(シンク)を避ける
一番の対策は、「HTMLとして解釈させる機能を使わないこと」です。
innerHTML の代わりに、textContent を使いましょう。これなら、たとえ攻撃者がHTMLタグを送ってきても、ブラウザはそれを「単なる文字」として表示するだけで、スクリプトとしては実行しません。
// 安全な書き方:textContentならHTMLタグを無害化できる
document.getElementById(‘welcome’).textContent = “こんにちは、” + fragment + “さん!”;
これだけで、家の中でのスクリプト実行は阻止できます。
その②:CSP(コンテンツセキュリティポリシー)で壁を作る
さらに強固な防犯として、CSP(Content Security Policy)というヘッダーを設定します。これは、サーバーからブラウザに「このサイトでは、許可していない外部スクリプトは絶対に実行しちゃダメだよ!」と命令する仕組みです。
サーバーのレスポンスヘッダーに以下を追加するイメージです。
CSPの設定例
Content-Security-Policy: default-src ‘self’; script-src ‘self’; object-src ‘none’;
default-src 'self': 基本的に自分のドメインからの読み込みしか許さない。object-src 'none': プラグイン(Flashなど)の実行を禁止する。
こうしておけば、仮にコードに隙があっても、攻撃者が外部の悪意あるサイトからスクリプトを読み込もうとしてもブラウザがブロックしてくれます。まさに「侵入者に壁を作る」強力な防犯装置ですね。
—
最後に:セキュリティは「疑う」ことから始まる
DOM型XSSは、サーバーサイドのログには残りません。だからこそ、開発者である私たちが「ブラウザ上で動くデータは、すべてユーザーが書き換えられる可能性がある」と疑うことが、最強の防御になります。
「あ、これURLのパラメータをそのまま表示しちゃってるかも?」と気づいたとき、あなたはもう立派なセキュリティエンジニアの第一歩を踏み出しています。
まずは、自分の書いたコードで innerHTML や eval() を使っている箇所がないか、一度チェックしてみてください。一歩ずつ、安全なアプリケーションを作っていきましょう!
何か不明な点や、さらに深い技術的背景を知りたい場合は、いつでも聞いてくださいね。応援しています!
コメント