【実務・中級編】クロスサイトスクリプティング(XSS)の攻撃ベクトルとコンテキスト依存の出力エンコーディング – アプリケーションセキュリティ & 安全な開発防御ガイド

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は検知するが、論理的な脆弱性は防げない。アプリケーション層でのエスケープこそが、真のセキュリティだ。

セキュリティとは、ツールを入れることではない。「データに対する不信感」をコードの細部にまで宿らせることだ。今日から、君の書くコードの一つ一つに、その疑念を込めてほしい。それが、君自身とユーザーを守る唯一の道だ。

コメント

タイトルとURLをコピーしました