XSSは「死んだ」のか?:コンテキストを理解しない者は、現場で即死する
「XSS? 今どきフレームワークが勝手にエスケープしてくれるでしょ?」
インシデント対応の現場で、若いエンジニアからよく聞く言葉だ。だが、答えは「No」だ。フレームワークが自動化してくれるのは、あくまで「HTMLボディの中」という安全圏の話であって、開発者が安易に動的生成したJavaScript内や、CSSの属性値にデータを流し込んだ瞬間、その防御壁は脆くも崩れ去る。
今日は、教科書的な「サニタイズしましょう」という綺麗事ではなく、攻撃者がどうやってその「自動防御」をバイパスし、セッションを奪取するのか、その泥臭い現実と対策を叩き込む。
—
1. 攻撃者が狙う「コンテキスト」の盲点
XSSの怖さは、データが「どこに」出力されるかによって、攻撃ベクトルが劇的に変化する点にある。
HTMLコンテキスト(基本)
`
` のような場所。ここなら `htmlspecialchars()` で十分だ。しかし、攻撃者はここを狙わない。なぜなら、ここに対する防御は最も普及しているからだ。
JavaScriptコンテキスト(最大の罠)
// 脆弱な実装例
const username = ‘‘;
ここでユーザー入力に `’ ; alert(document.cookie); //` を入れられたらどうなるか。
`const username = ” ; alert(document.cookie); //’;`
見事にコードが分断され、任意のJSが実行される。`htmlspecialchars`を通しても、シングルクォートがHTMLエンティティに変換されるだけでは不十分な場合が多い。JSコンテキストでは、JS専用のUnicodeエスケープが必要だ。
DOM-based XSS(現代の主戦場)
サーバーサイドを介さず、クライアント側のJSがURLフラグメント(`location.hash`など)を読み込み、それを`innerHTML`に流し込むパターン。WAFもサーバーログもすり抜けるため、最も検出が難しい。
—
2. 実践的:セキュアな実装パターン
各コンテキストに応じた「防弾」の実装を提示する。
A. JavaScriptコンテキストへの安全な埋め込み(PHP)
JSON形式で安全にJSへ渡すのが鉄則だ。
B. DOM-based XSSを防ぐ(JavaScript)
`innerHTML` は「毒」である。可能な限り `textContent` を使うこと。
// 脆弱な実装:innerHTMLはタグを解釈してしまう
// const div = document.getElementById(‘output’);
// div.innerHTML = userInput;
// 安全な実装:textContentなら全て文字列として扱う
const div = document.getElementById(‘output’);
div.textContent = userInput;
—
3. 防御の要:Content Security Policy (CSP)
どれだけ気をつけても、人的ミスはゼロにならない。最後の防衛線がCSPだ。これを設定していないのは、鍵の開いた金庫を道端に置いているのと同じだ。
以下は、Nginxで設定する推奨のCSPヘッダー例である。
Nginx設定ファイルに追加
‘unsafe-inline’を排除し、インラインスクリプトを原則禁止する
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’; object-src ‘none’; frame-ancestors ‘none’; base-uri ‘self’;”;
- script-src ‘self’: 外部ドメインからのスクリプト読み込みを禁止。
- object-src ‘none’: Flashなどの古いプラグインによる攻撃を封鎖。
- frame-ancestors ‘none’: クリックジャッキング対策。
—
現場のエンジニアへ送る「鉄の掟」
1. 「出力先」を常に意識しろ: データを表示する場所がHTMLタグ内なのか、属性値なのか、JSの変数内なのか。それによって使うべき関数は変わる。
2. innerHTMLはコードレビューで即却下: `innerHTML`を使用する箇所は、プロジェクト内で例外なく禁止すべきだ。例外が必要なら、DOMPurifyのような堅牢なライブラリを介してサニタイズせよ。
3. WAFは「お守り」ではない: WAFは検知するが、論理的な脆弱性は防げない。アプリケーション層でのエスケープこそが、真のセキュリティだ。
セキュリティとは、ツールを入れることではない。「データに対する不信感」をコードの細部にまで宿らせることだ。今日から、君の書くコードの一つ一つに、その疑念を込めてほしい。それが、君自身とユーザーを守る唯一の道だ。

コメント