Webキャッシュポイズニング:その「親切心」がシステムの致命傷になる
現場でインシデント対応をしていると、往々にして「なぜこんな巧妙な攻撃ができるのか」と驚かれることがある。だが、Webキャッシュポイズニングの正体は、実は攻撃者の高度な技術というより、「システムの親切すぎる仕様」を逆手に取ったハックに過ぎない。
Webキャッシュは、本来ユーザーに速いレスポンスを届けるための「善意の仕組み」だ。しかし、攻撃者はそのキャッシュサーバーの「学習能力」を悪用し、特定のユーザーだけでなく、全ユーザーのブラウザに偽のコンテンツを送り込む。いわば、Webサイトの「共有メモリ」を汚染して、皆の画面を書き換える行為だ。
今日は、この「キャッシュ汚染」がなぜ起き、どうすれば根絶できるのか、現場の最前線から解説する。
—
1. なぜ「汚染」は起きるのか?
キャッシュサーバー(Nginx, Varnish, Cloudflareなど)は、リクエストを区別するために「キャッシュキー」を使う。通常、これはリクエストのパス(/index.htmlなど)だが、問題は「キャッシュキーに含まれないヘッダー」がレスポンスに影響を与える場合だ。
攻撃者は、次のようなヘッダーを注入する。
X-Forwarded-HostX-Forwarded-SchemeX-Original-URL
もしバックエンドのアプリが、これらのヘッダーを信頼して「動的にパスを生成」したり「外部スクリプトの読み込み元(src属性)」を変更したりしていたら、それが即座にトリガーとなる。
攻撃のPoC(概念実証)
攻撃者は、ターゲットのキャッシュサーバーに対し、細工したヘッダーを送る。
GET /login HTTP/1.1
Host: target-site.com
X-Forwarded-Host: evil-attacker.com
バックエンドがこの値を拾い、src="https://X-Forwarded-Host/malicious.js" のようなHTMLを生成して返したとする。キャッシュサーバーは「お、これは /login のレスポンスだな。キャッシュしておこう」と保存する。その瞬間、次からサイトにアクセスする全ユーザーのブラウザ上で、攻撃者のJavaScriptが実行されることになる。
—
2. 防御の要:キャッシュキーの厳格な管理
この脆弱性を叩き潰す唯一の近道は、「キャッシュの判断基準を明確にする」ことだ。
Nginxの設定:ヘッダーの正規化とホワイトリスト化
Nginxをリバースプロキシとして使っているなら、不要なヘッダーをバックエンドに渡さないのが鉄則だ。fastcgi_param や proxy_set_header で、信頼できないヘッダーをクリアする。
# nginx.conf の例
# 攻撃者が悪用しそうなヘッダーを強制的に無効化・固定する
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
# もし特定のヘッダーが不要なら、空にする
proxy_set_header X-Original-URL "";
アプリケーションコードでのバリデーション
アプリ側でも、外部からのヘッダーを安易に信用してはいけない。特に X-Forwarded-Host を使ってURLを組み立てるような設計は、今すぐ廃止すべきだ。
【ダメな実装例(PHP)】
// 警告:これはキャッシュポイズニングの温床です
$host = $_SERVER['HTTP_X_FORWARDED_HOST'] ?? $_SERVER['HTTP_HOST'];
echo "<script src='https://{$host}/assets/js/app.js'></script>";
【セキュアな実装例(PHP)】
// 許可されたドメイン以外は無視するホワイトリスト設計
$allowed_hosts = ['www.example.com', 'api.example.com'];
$host = $_SERVER['HTTP_HOST'];
if (!in_array($host, $allowed_hosts)) {
// 異常なリクエストとして弾くか、デフォルト値に強制する
$host = 'www.example.com';
}
echo "<script src='https://{$host}/assets/js/app.js'></script>";
—
3. なぜ「設定」だけでは不十分なのか
多くのエンジニアが陥る罠は、「WAFを入れていれば大丈夫」という過信だ。WAFは特定のパターンを弾くことはできるが、キャッシュポイズニングのバリエーション(HTTPパラメータ汚染など)は非常に多岐にわたる。
実践的な防御チェックリスト
1. キャッシュキーの可視化: 使用しているCDNやキャッシュサーバーで、実際に「何をキーとしてキャッシュしているか」をログ出力または管理画面で確認せよ。
2. Varyヘッダーの活用: レスポンスヘッダーに Vary: X-Forwarded-Host を含めることで、キャッシュサーバーに「このヘッダーの値が違えば別物として扱え」と指示できる。
header('Vary: X-Forwarded-Host, Accept-Encoding');
3. 不必要なヘッダーの破棄: アプリ側で使わないヘッダーは、ロードバランサーやNginxの段階で全て破棄(proxy_set_header HeaderName "")する。
—
最後に:セキュリティは「疑うこと」から始まる
キャッシュポイズニングは、一度成功すれば被害範囲が全ユーザーに及ぶ「破壊力の高い攻撃」だ。しかし、防御の基本はシンプル。「外部から来たヘッダーは、たとえ X- で始まっていても決して信頼しない」という姿勢を持つこと。
コードを書くとき、インフラを組むとき、常に自問自答してほしい。「この値が攻撃者によって書き換えられたら、システムはどう反応するか?」と。その疑念こそが、最強のファイアウォールになる。
もし、今運用しているシステムで X-Forwarded-Host を無批判に扱っている箇所を見つけたら、それが今日、あなたが修正すべき最も重要なコードだ。健闘を祈る。
コメント