Web Cache Poisoning and Cache Deception Explained
Caches make websites fast by storing responses and serving them to many users. That same efficiency becomes a weapon if an attacker can get a malicious or private response stored in the cache.
Web cache poisoning
Caches decide whether two requests are "the same" using a cache key, usually the method, host and path, sometimes query string. Headers that are not part of the key, but still affect the response, are called unkeyed inputs.
If an unkeyed header changes the response, an attacker can send a request with a malicious value, get the response cached, and have it served to everyone who requests the same URL.
Example
An application uses X-Forwarded-Host to build script URLs:
<script src="https://{X-Forwarded-Host}/static/app.js"></script>
The attacker sends:
GET / HTTP/1.1
Host: www.example.com
X-Forwarded-Host: evil.example
If the CDN caches this response, every visitor to the homepage now loads JavaScript from the attacker.
Web cache deception
Cache deception is the reverse: tricking the cache into storing a private response so the attacker can read it.
- The attacker sends the victim a link like
https://app.example.com/account/settings/x.css. - The application ignores the extra path and returns the victim's settings page.
- The CDN sees
.cssand caches the response as a static file. - The attacker requests the same URL and receives the victim's private data from the cache.
Variants use path delimiters, encoded characters and normalisation differences between the cache and the origin.
How to prevent cache poisoning
- Do not use unkeyed headers like
X-Forwarded-Hostto generate content, unless set by your own trusted proxy and stripped from client requests. - Add inputs that change the response to the cache key, or use
Varyappropriately. - Disable caching for dynamic, personalised pages.
- Keep frameworks and CDNs updated, as many poisoning vectors are framework-specific.
How to prevent cache deception
- Cache based on
Cache-Controlheaders from the origin, not on file extensions. - Send
Cache-Control: no-store, privateon every authenticated or personalised response. - Return 404 for unexpected path suffixes instead of ignoring them.
- Align path normalisation between your CDN and application.
Safe caching defaults
| Content | Cache-Control |
|---|---|
| Hashed static assets | public, max-age=31536000, immutable |
| Public pages | public, max-age=60 or as appropriate |
| Authenticated pages and APIs | no-store, private |
Testing caching behaviour
Testing identifies cache behaviour via response headers, probes unkeyed inputs with harmless markers using unique cache busters, and checks whether private pages can be cached under static-looking paths. SecurityInfinity runs these checks safely without poisoning real users' responses. Start free.
Frequently asked questions
Can cache poisoning affect all users?
Yes. A single poisoned entry can be served to every visitor until it expires, which is what makes it dangerous.
Is cache deception a server or CDN problem?
It arises from the interaction between the two. Fixing either side's behaviour, ideally both, prevents it.
Does Cache-Control: private stop CDNs caching?
Shared caches such as CDNs should not store responses marked private, and no-store prevents storage entirely. Verify your CDN honours origin headers.