← All cybersecurity posts

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.

  1. The attacker sends the victim a link like https://app.example.com/account/settings/x.css.
  2. The application ignores the extra path and returns the victim's settings page.
  3. The CDN sees .css and caches the response as a static file.
  4. 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

  1. Do not use unkeyed headers like X-Forwarded-Host to generate content, unless set by your own trusted proxy and stripped from client requests.
  2. Add inputs that change the response to the cache key, or use Vary appropriately.
  3. Disable caching for dynamic, personalised pages.
  4. Keep frameworks and CDNs updated, as many poisoning vectors are framework-specific.

How to prevent cache deception

  1. Cache based on Cache-Control headers from the origin, not on file extensions.
  2. Send Cache-Control: no-store, private on every authenticated or personalised response.
  3. Return 404 for unexpected path suffixes instead of ignoring them.
  4. Align path normalisation between your CDN and application.

Safe caching defaults

ContentCache-Control
Hashed static assetspublic, max-age=31536000, immutable
Public pagespublic, max-age=60 or as appropriate
Authenticated pages and APIsno-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.

Find what attackers can see, before they do.

AI pentesting agents test your web app, API and cloud, and every finding comes with evidence and a fix. First report in about 10 minutes.

Stay ahead of attackers.

New vulnerabilities, breach write-ups and practical fixes. A few emails a month.