1. キャッシュキーとキャッシュ可能性 #cache-key
CDN(Cloudflare、Akamai、CloudFront 等)や Varnish 等のリバースプロキシは、レスポンスをキャッシュキー(通常は URL のパス + クエリ + ホスト)で識別して保管します。同じキーへのリクエストにはキャッシュから即返答するので、サーバ負荷が下がり、応答が速い。
// キャッシュキー = (Method + Host + Path + Query) のハッシュ GET https://shop.utsuroi.example.com/products/nagi ↓ キャッシュキー: "shop.utsuroi.example.com/products/nagi" ↓ 初回: オリジンサーバから取得 → キャッシュ ↓ 2回目以降: キャッシュから即返答
2. Unkeyed Input(キャッシュキーに含まれない入力) #unkeyed-input
レスポンス生成に影響するが、キャッシュキーに含まれないリクエスト要素。
典型的には HTTP リクエストヘッダの一部:X-Forwarded-Host、X-Original-URL、User-Agent、Cookie 等。これらがサーバ側でレスポンスに反映されるのに、キャッシュキーには含まれていないと攻撃が成立する。
例:アプリが X-Forwarded-Host を信用してリンクを生成 GET /home HTTP/1.1 Host: shop.utsuroi.example.com X-Forwarded-Host: evil.com ← unkeyed input オリジンの応答(キャッシュされる): <link rel="canonical" href="https://evil.com/home"> <script src="https://evil.com/static/main.js"></script> → 後続の正規ユーザがこのレスポンスを受ける → 攻撃者の JS が実行
3. 典型的な攻撃 #typical-attacks
- Stored XSS の量産配信 — 1回のキャッシュ汚染で全ユーザに XSS
- リダイレクト先の改ざん — Location ヘッダに攻撃者ドメインを混入
- DoS — エラーレスポンスをキャッシュさせて全ユーザに 500 を返す
- 機密情報の混入 — 認証ヘッダで個別化されるはずの内容がキャッシュキー無視で全員に配信
4. 正しい防御 #defense
- キャッシュキーに「レスポンスに影響する全要素」を含める — Cloudflare の Cache Key カスタマイズ、Varnish の
vcl_hashで明示 - CDN/プロキシで信頼できないヘッダを破棄 —
X-Forwarded-Host、X-Original-URL、X-Rewrite-URL等を CDN レイヤで除去 - アプリケーション側で信頼できないヘッダを使わない —
$_SERVER['HTTP_X_FORWARDED_HOST']ではなく、設定ファイルの canonical URL を使う - キャッシュ可能なエンドポイントを限定 — 動的・個別化されるエンドポイントは
Cache-Control: no-store - 監視 — キャッシュヒット率、レスポンスサイズの異常、Vary ヘッダの不一致をモニタリング