【実務・中級編】クロスサイトスクリプティング(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をコピーしました