Web Cache Poisoning & Deception
Abuse caching layers to serve attacker-controlled responses (poisoning) or to capture victims' private data (deception) in CTF web challenges.
Web Cache Poisoning & Deception
Caches sit between the client and the origin server (CDNs like Cloudflare, Akamai, Fastly, reverse proxies like Varnish/nginx, and application caches) to serve a stored response to many users. Two distinct attack classes abuse that shared storage:
- Web cache poisoning — you trick the cache into storing a malicious response that is then served to other victims. The payload travels outward.
- Web cache deception — you trick the cache into storing a victim's private response (their account page, API token) so that you can fetch it from the cache key. The data travels inward to you.
Both exploit the same root cause: a discrepancy between how the cache and the origin decide what a request "is" — what gets stored, and under which key.
Foundational reading
PortSwigger's Practical Web Cache Poisoning and the 2024 Black Hat paper Gotta cache 'em all are the canonical sources, alongside the Web cache poisoning and Web cache deception Web Security Academy paths.
Cache Keys: Keyed vs Unkeyed Inputs
When a cache receives a request it builds a cache key — a fingerprint of the
parts of the request it considers significant. Typically this is the request line
(method + path) and the Host header, sometimes a few more. If a later request
produces the same key, the cache serves the stored response without contacting
the origin.
Everything not in the key is an unkeyed input. If the origin reflects or acts on an unkeyed input but the cache ignores it for keying, you have the core primitive for poisoning: you send a request whose key matches what victims will request, but whose unkeyed input carries your payload. The poisoned response is cached and served to everyone.
Cache key = GET /home + Host: example.com <-- what the cache compares
Unkeyed = X-Forwarded-Host, X-Forwarded-Scheme, <-- ignored for keying but
X-Host, cookies, some query params may reach the originConfirm caching first
Look for response headers that prove a cache is in play and whether your
request was a hit: X-Cache: hit/miss, CF-Cache-Status,
Age, X-Cache-Hits, Cache-Control. A
rising Age and a hit after your first request mean the
response is being stored.
Finding Unkeyed Inputs with Param Miner
Param Miner is the open-source Burp extension that automates discovery of unkeyed headers, cookies, and query params. It guesses names from a large wordlist and watches whether each one changes the response while not changing the cache key.
Send the target request to Burp Repeater and add a cache buster — a unique,
keyed value so your probes never collide with the real cached entry, e.g.
GET /home?cb=8f3a1 or a unique Host. This stops you from poisoning the real
key while testing.
Right-click the request and choose Extensions → Param Miner → Guess headers (and Guess cookies / Guess GET parameters). Param Miner sends its wordlist of header names and diffs the responses.
Review the Issues / extension output for "Identified the following secret
input" findings, e.g. X-Forwarded-Host, X-Forwarded-Scheme, X-Original-URL,
X-Host. These are candidate unkeyed inputs.
For each candidate, confirm in Repeater that it (a) changes the response and (b) does not change the cache key (the response can be retrieved by a clean request that omits the header).
The "cache buster" mindset
Never test poisoning against the path real victims hit. Always work behind a buster query/parameter, then only "fire for effect" against the live key once your payload is fully built — otherwise you DoS yourself or alert defenders.
Classic Reflected-Header Poisoning
The textbook case: the origin reflects an unkeyed header into the response body, often when building absolute URLs for assets.
The origin uses X-Forwarded-Host to construct an absolute script src:
<!-- origin response, host taken from X-Forwarded-Host -->
<script src="https://example.com/static/analytics.js"></script>Other classic reflections: X-Forwarded-Scheme/X-Forwarded-Proto to force an
insecure redirect loop, X-Original-URL/X-Rewrite-URL to change routing, and
reflected unkeyed query parameters.
Encoding matters
The cache may key on the raw query string while the origin URL-decodes it, or
vice versa. A value that is harmless to the cache key but decodes to a payload
at the origin (e.g. ?param=valid&utm=%22><script>...)
is a recurring poisoning trick. Mind which layer normalizes.
Web Cache Deception via Path Confusion
Deception flips the goal: instead of pushing your payload out, you cache someone else's sensitive page. You craft a URL that the origin treats as the dynamic, authenticated endpoint, but the cache treats as a static, cacheable file. Send the victim that URL (or have the admin bot visit it); their private response gets stored under a key you can then request unauthenticated.
The discrepancy lives in how each layer parses paths. The PortSwigger 2024 research Gotta cache 'em all catalogs these as cache rules the attacker subverts.
Origin vs cache normalization labs
The Web Security Academy distinguishes origin server normalization (origin decodes/resolves, cache does not) from cache server normalization (cache decodes, origin does not). Determine which side normalizes by probing how each layer responds to an encoded dot-segment or delimiter, then pick the payload that desynchronizes them.
Fat GET
Some frameworks read request parameters from both the query string and the
body, even on a GET. A "fat GET" smuggles a body the cache ignores for keying
but the origin parses:
GET /home?param=valid HTTP/1.1
Host: example.com
Content-Length: 23
param="><script>...</script>The cache keys on the path/query only; the origin reflects the body parameter
into the cached page. Same idea applies to parameter pollution where the cache
keys on the first occurrence and the origin uses the last (?x=1&x=payload).
DoS via Cache Poisoning
You do not always need code execution — poisoning the cache with a broken response is a denial of service for every subsequent visitor.
GET / HTTP/1.1
Host: example.com
X-Forwarded-Host: a."><script>x</script> # breaks page rendering for all
GET / HTTP/1.1
Host: example.com
X-Oversized-Header-Value: AAAA...AAAA # origin 400s; the 400 gets cachedCache-poisoned DoS (CPDoS) variants: oversized headers, illegal header
characters, or a poisoned HTTP method override that makes the origin return an
error which is then cached and served to all users.
Chaining
Cache poisoning is most powerful as a delivery mechanism for another bug, turning a self-only or low-impact issue into a cache-wide mass exploit.
- + XSS — a reflected-header or unkeyed-param reflection that you could only fire against yourself becomes stored, persistent XSS against every cache consumer. See Reflected XSS and DOM XSS.
- + Open redirect — poison a cached
Locationso every visitor is bounced to your phishing/credential page. - + Cookie/header reflection — reflect a cookie value into the body to cache a payload, or chain with CSP Bypass when an unkeyed header controls a script source the CSP otherwise allows.
Defenses
- Prefer not caching authenticated or per-user responses at all; segment static and dynamic origins so a static cache can never see private content.
- Cache only by an explicit allowlist of paths/extensions, not by guessing.
Checklist
- Confirm a cache is present (
X-Cache,Age,CF-Cache-Status) and observe hit/miss behavior. - Add a cache buster before testing; never probe the live key.
- Run Param Miner to enumerate unkeyed headers, cookies, and parameters.
- For each unkeyed input, verify it changes the response but not the cache key.
- Test reflected-header poisoning (
X-Forwarded-Host,X-Forwarded-Scheme,X-Original-URL). - Try fat GET and parameter-pollution reflections.
- For deception: find a dynamic endpoint and confuse the path with
.css/.jssuffixes,/static/..%2ftraversal, and;/%23/%3f/%0adelimiters. - Determine whether the cache or the origin normalizes encoded segments.
- Consider DoS via oversized/illegal headers if a malformed response is cacheable.
- Chain with XSS or open redirect for a cache-wide payload.
See Also
- Prototype Pollution — another way to influence shared state
- HTTP Request Smuggling — a related front-end/back-end desync class
- Reflected XSS — the payload you cache
- CSP Bypass — when a poisoned script source must defeat CSP
- Recon — fingerprinting CDNs and caching layers
Last updated on
File Upload Vulnerabilities
Bypass file upload filters in CTF web - extensions, MIME, magic bytes, polyglots, path tricks, and unsafe storage.
Dependency Confusion
Hijack software supply chains by publishing public packages that shadow private internal package names across npm, pip, Maven, and friends.