【実務・中級編】 Webキャッシュポイズニングによるレスポンス汚染 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

Webキャッシュポイズニング:その「親切心」がシステムの致命傷になる

現場でインシデント対応をしていると、往々にして「なぜこんな巧妙な攻撃ができるのか」と驚かれることがある。だが、Webキャッシュポイズニングの正体は、実は攻撃者の高度な技術というより、「システムの親切すぎる仕様」を逆手に取ったハックに過ぎない。

Webキャッシュは、本来ユーザーに速いレスポンスを届けるための「善意の仕組み」だ。しかし、攻撃者はそのキャッシュサーバーの「学習能力」を悪用し、特定のユーザーだけでなく、全ユーザーのブラウザに偽のコンテンツを送り込む。いわば、Webサイトの「共有メモリ」を汚染して、皆の画面を書き換える行為だ。

今日は、この「キャッシュ汚染」がなぜ起き、どうすれば根絶できるのか、現場の最前線から解説する。

—

1. なぜ「汚染」は起きるのか?

キャッシュサーバー(Nginx, Varnish, Cloudflareなど)は、リクエストを区別するために「キャッシュキー」を使う。通常、これはリクエストのパス(/index.htmlなど)だが、問題は「キャッシュキーに含まれないヘッダー」がレスポンスに影響を与える場合だ。

攻撃者は、次のようなヘッダーを注入する。

  • X-Forwarded-Host
  • X-Forwarded-Scheme
  • X-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 を無批判に扱っている箇所を見つけたら、それが今日、あなたが修正すべき最も重要なコードだ。健闘を祈る。

コメント

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