蓄積型XSSの恐怖:データベースが「凶器」に変わる瞬間
現場で多くのインシデントレスポンスを担当してきたが、最もタチが悪い攻撃の一つが「蓄積型XSS(Stored XSS)」だ。SQLインジェクションが「データベースの情報を盗む」攻撃なら、蓄積型XSSは「データベースを乗っ取り、閲覧者全員を攻撃者に変える」攻撃だ。
一度でも悪意のあるスクリプトがDBに紛れ込めば、管理者がいくらPCをクリーンに保っていても、その掲示板やプロフィールページを開いた瞬間にブラウザのセッションが盗まれる。今回は、この「見えない汚染」をどう防ぎ、どう開発現場の文化に落とし込むか、泥臭い実務の視点で解説する。
—
1. なぜ「蓄積型」は終わらないのか(メカニズムの理解)
蓄積型XSSの恐ろしさは「汚染の永続性」にある。
1. 注入: 攻撃者が掲示板の投稿フォーム等に のようなコードを投げ込む。
2. 蓄積: アプリケーションがサニタイズを怠ると、このスクリプトがそのままDBに保存される。
3. 拡散: 別のユーザー(管理者含む)が該当ページを閲覧すると、ブラウザがDBから送られてきた文字列を「プログラム」として解釈し実行する。
ここで重要なのは、攻撃者は一度投稿するだけでいいという点だ。防御側は、その投稿を削除するまで、サイトを訪れるすべてのユーザーのリスクを背負い続けることになる。
—
2. 対策の核心:出力エンコーディングの徹底
よくある誤解だが、「入力時にバリデーション(フィルタリング)する」だけで防ごうとするのは甘い。入力時にタグを削除する手法は、システムの仕様変更や文字コードの解釈の違いで簡単にバイパスされるからだ。
「防御の鉄則は、出力する瞬間にそのコンテキストに合わせてエスケープする」こと。これに尽きる。
PHPでの実装例:モダンなフレームワークを使わない現場の知恵
フレームワーク(Laravel等)を使っていれば {{ $variable }} で自動エスケープされるが、レガシーな環境や素のPHPを書く際は、以下の関数を徹底してほしい。
/
function h($str) {
return htmlspecialchars($str, ENT_QUOTES, ‘UTF-8’);
}
// 出力時:必ず h() を通す
echo “
“;
?>
JavaScriptでの実装例:DOM破壊を防ぐ
JSで動的にDOMを生成する場合、innerHTML は禁忌だ。必ず textContent を使う癖をつけよう。
// 危険:innerHTMLを使うと、scriptタグが実行されるリスクがある
// document.getElementById(‘comment’).innerHTML = userInput;
// 安全:textContentなら文字列としてのみ扱われる
document.getElementById(‘comment’).textContent = userInput;
—
3. 「多層防御」としてのCSP(Content Security Policy)
コードレベルの修正に加え、インフラ側で「万が一」に備えるのがプロの仕事だ。HTTPレスポンスヘッダに Content-Security-Policy を設定し、信頼できないスクリプトの実行を物理的にブロックする。
Nginxの設定例:
信頼できるドメインからのスクリプトのみ許可し、インラインスクリプトを禁止する
add_header Content-Security-Policy “default-src ‘self’; script-src ‘self’ https://trusted.cdn.com; object-src ‘none’;”;
default-src 'self': 自分のドメイン以外からの読み込みを禁止。script-src 'self': インラインスクリプト(等)の実行をブラウザ側で拒否する。これだけで蓄積型XSSの被害を劇的に減らせる。
—
4. セキュリティチーフからの「現場の心得」
最後に、技術的な実装以上に大切なことを伝える。
1. レビューの自動化: 手動レビューには限界がある。SonarQubeやSnykなどの静的解析ツール(SAST)をCI/CDパイプラインに組み込み、innerHTML やエスケープ漏れを機械的に検知させること。
2. 「信用」をコードに書かない: APIから来たデータであっても、自サイトに表示するならそれは「汚染されている」と見なすのがデフォルトだ。
3. インシデントは「資産」: もし脆弱性が見つかったら、それを隠すのではなく「なぜそのコードが書かれたのか」をチームで共有し、開発フロー自体を改善する材料にしてほしい。
セキュリティは、一人の天才が防ぐものではなく、チームの「当たり前」のレベルを一段上げることで構築される。まずは今日、手元のプロジェクトで innerHTML が使われていないか、あるいはCSPヘッダが飛んでいるかを確認することから始めてみてほしい。
それが、あなたのサービスとユーザーを守るための、最も確実な第一歩だ。
コメント