Webキャッシュ汚染(Web Cache Poisoning):キャッシュサーバーを「凶器」に変える手口と防衛術
やあ。現場の最前線でインフラを支える諸君、お疲れ様。
今日は、多くのエンジニアが「CDNやキャッシュサーバーは速くするためのもの」と高を括っている裏側で、静かに牙を研いでいる恐ろしい攻撃手法――Webキャッシュ汚染(Web Cache Poisoning)について話そう。
これは単なるWebアプリの脆弱性じゃない。ユーザーのブラウザとサーバーの間に立つ「仲介者」を騙し、君たちの正当なアプリケーションを、攻撃者好みの「汚染されたコンテンツ」へと書き換えてしまう、極めて破壊的な手法だ。
—
なぜキャッシュサーバーが「共犯者」になるのか
Webキャッシュ汚染の核心は、「キャッシュキーの不一致」にある。
キャッシュサーバー(Nginx, Cloudflare, Varnish等)は、リクエストのどの要素を「キー(識別子)」として扱うかを決定している。通常はURLだが、攻撃者はここに「キャッシュには含まれないが、アプリ側では処理されるヘッダー」をねじ込む。
例えば、X-Forwarded-Host ヘッダーをアプリが信頼して動的にJavaScriptの読み込み元(src)を生成している場合、攻撃者は以下のようにリクエストを投げる。
1. 攻撃者が X-Forwarded-Host: evil-attacker.com を付与してリクエスト。
2. アプリ側はこれを受け取り、script src="https://evil-attacker.com/malicious.js" というHTMLを生成。
3. キャッシュサーバーは、この「悪意あるレスポンス」をキャッシュキー(URLのみ)と紐付けて保存。
4. 次にやってきた無実の一般ユーザーに対し、キャッシュサーバーはキャッシュされている「悪意あるレスポンス」を配布する。
こうなると、被害者は君たちのドメイン上で、攻撃者のスクリプトを堂々と実行することになる。セッションハイジャックや認証情報の窃取など、やりたい放題だ。
—
攻撃シーケンス:こうしてシステムは汚染される
現場の防御を考えるために、まずは攻撃者が狙う「隙」を確認しよう。
- Unkeyed Inputs(非キャッシュキー入力)の発見:
X-Forwarded-Host,X-Original-URL,X-Rewrite-URLといったヘッダーが、アプリのレスポンスに反映されるか確認する。 - キャッシュの強制更新:
Cache-Control: no-storeを無視させるような工夫や、キャッシュサーバーが更新タイミングを誤る挙動(バグ)を突く。 - 汚染の永続化: 多くのユーザーにヒットする人気コンテンツ(トップページやログイン画面)をターゲットにし、キャッシュの寿命(TTL)が切れるまで攻撃を継続させる。
—
実践:防御のための堅牢な実装
「ヘッダーを信頼しない」というのが鉄則だが、具体的にどう書くか。まずはPHPでの実装例を見てくれ。
【NG】信頼してはいけない実装
// 絶対にやってはいけない:ヘッダーをそのままURL生成に使う
$host = $_SERVER['HTTP_X_FORWARDED_HOST'] ?? 'default.example.com';
echo '<script src="https://' . $host . '/assets/app.js"></script>';
【推奨】許可リスト(Whitelist)方式の実装
/**
* 安全なホスト名取得関数
*/
function get_safe_host() {
$allowed_hosts = ['static.example.com', 'cdn.example.com'];
$requested_host = $_SERVER['HTTP_X_FORWARDED_HOST'] ?? '';
// 許可リストになければデフォルトを返す
if (in_array($requested_host, $allowed_hosts, true)) {
return $requested_host;
}
return 'static.example.com'; // 安全なフォールバック先
}
// テンプレート内での使用
$safe_host = get_safe_host();
echo '<script src="https://' . htmlspecialchars($safe_host, ENT_QUOTES) . '/assets/app.js"></script>';
—
インフラ層での防御:Nginx/WAFの設定
アプリの修正だけでなく、ゲートウェイでの制限も必須だ。Nginxで不要なヘッダーを削除する設定を推奨する。
Nginxのヘッダー除去設定
# キャッシュ汚染の原因となるヘッダーをアップストリームに渡さない
proxy_set_header X-Forwarded-Host "";
proxy_set_header X-Original-URL "";
# もし必要なら、明示的にホワイトリスト以外のヘッダーを無視する
# 信頼できるヘッダーのみを透過させる
proxy_set_header Host $host;
WAF(AWS WAF等)での対策
WAFを使用している場合、X-Forwarded-Host のようなヘッダーに「予期せぬドメイン名が含まれていないか」を監視するルールを適用せよ。特に、正規表現で「自社ドメイン以外」の値をブロックするのは非常に効果的だ。
—
最後に:セキュリティエンジニアとしての心得
Webキャッシュ汚染を防ぐことは、単なるコードの修正ではない。「外部からの入力を、どの層が信頼して処理しているのか」というデータフローの可視化だ。
開発チームにはこう伝えてほしい。
「ヘッダーは、ユーザーが自由に変えられる『ただのテキスト』だ。それを信頼してドメインやパスを生成することは、家の鍵を道端に置くのと同じことだ」と。
キャッシュサーバーを味方につけ、堅牢なシステムを構築する。それが我々プロフェッショナルの仕事だ。質問があればいつでも持ってきてくれ。共に堅牢なシステムを作り上げよう。
コメント