What Is Web Cache Poisoning? How Can a Cached Response Become a Security Problem?
What Is Web Cache Poisoning?
A user requests /homepage. The origin server generates a response. A cache stores it. The next user requests /homepage. The cache returns the stored response without touching the origin. That's caching working as intended. It reduces latency, saves origin load, and serves popular content efficiently. Nothing suspicious.
What the Cache Is Actually Doing
The request path for a typical web application doesn't go directly from browser to origin. It passes through intermediaries:
Browser โ CDN / reverse proxy / cache โ Origin server
When a request arrives at the cache, it needs to decide: do I have a stored response for this? To answer that, it computes something called a cache key: a representation of the request that determines which stored responses are candidates for reuse. A simple cache key might be just the URL path:
GET /profile
Cache key: /profile
Cache miss โ forward to origin โ store response โ return response. Next request for /profile โ cache hit โ return stored response.
The Cache Key vs the Origin's Interpretation
The cache uses its cache key to decide equivalence. The origin server uses the full request to generate a response. These two things don't have to agree. If the origin uses some part of the request that the cache doesn't include in the key, two requests that the cache considers identical can produce different responses from the origin.
Consider a request that includes a header the origin uses to customize the response, but the cache doesn't factor that header into its key:
Request A: GET /page X-Custom-Header: value-a
Request B: GET /page X-Custom-Header: value-b
Cache key for both: /page
As far as the cache is concerned, these are the same request. As far as the origin is concerned, they're different. The origin might include the header's value in the response. The cache might store that response under the key /page. The next request for /page gets that stored response, even if it originated from a completely different input.
Walking Through the Lifecycle
Let's be precise about what happens:
- Attacker requests
/pagewith attacker-controlled input - Cache computes key:
/page(input not in key) - Cache MISS
- Request forwarded to origin
- Origin processes request including attacker-controlled input
- Origin generates response influenced by that input
- Cache stores response under key:
/page - Victim requests
/page - Cache computes key:
/page - Cache HIT
- Victim receives stored response
The cache isn't malfunctioning. It's doing exactly what it was configured to do: storing responses and serving them for matching keys. The problem is that the response it stored was generated under conditions the cache didn't capture in its key.
What Gets Poisoned and What Doesn't
This distinction matters: the cache itself isn't compromised. There's no unauthorized write to some cache database. The cache accepted a legitimate HTTP response from the origin and stored it. That's normal behavior. The security failure is the mismatch between what the cache considers equivalent and what the origin considers significant.
If the origin's response contains something derived from attacker-controlled input, and the cache stores that response and serves it to subsequent visitors, those visitors receive a response shaped by input they never supplied. What that means depends on what the response contains. If the attacker-controlled input ends up reflected in a response header or body in a way that affects clients, the consequences can range from unexpected behavior to more serious client-side impact.
Which Request Components Can Matter
Any part of the request that the origin uses to generate a response is potentially relevant. Whether that part is in the cache key determines whether the cache distinguishes between requests that differ in that part.
Things that might influence origin responses:
- URL path and query parameters
- The
Hostheader, which can affect routing and response generation - Custom headers that servers use for feature flags, A/B testing, or locale
- Content negotiation headers like
Accept-LanguageorAccept-Encoding - Request metadata passed between intermediaries
Multiple Layers Make This More Complex
Modern applications often have multiple caching layers between the browser and the origin:
Browser โ CDN โ Reverse proxy โ Application server
Each layer has its own view of the request and its own caching configuration. A CDN might key on the URL and a handful of standard headers. A reverse proxy in front of the application might key differently. The application itself might generate responses influenced by headers that neither caching layer keys on. This means the security assumptions made at one layer might not match the behavior at another.
Normal Caching vs Poisoning
The conceptual difference:
- Normal caching: Two requests mean the same thing to both the cache and the origin โ the cache reuses the response correctly.
- Cache poisoning: Two requests mean the same thing to the cache but different things to the origin โ the cache reuses a response that was generated under conditions different from what the later request would produce.
The vulnerability isn't "caching is bad." Caching is necessary and correct. The vulnerability is incorrect equivalence: the cache treating two requests as interchangeable when they produce meaningfully different responses.
Defenses
The defenses follow directly from the mechanism:
- Make cache keys reflect the inputs that matter. If the origin uses a request component to generate the response, that component should be part of the cache key, or the response shouldn't be cached in contexts where that component varies.
- Treat unkeyed inputs carefully. If a CDN or proxy passes headers to the origin without including them in the cache key, those headers are unkeyed inputs. Review whether the origin uses them in ways that affect the response.
- Don't cache responses that contain user-specific content unless that's explicit and intentional. Personalized responses shouldn't end up in shared caches.
- Configure caching deliberately. Default caching behavior may not match the security assumptions of the application. Cache key configuration and cacheable response criteria should be explicit decisions, not defaults inherited from intermediary software.
- Test with varied inputs. Cache behavior should be tested across different request variations, not just for functional correctness but to understand when different inputs produce different cache entries.
Comments
No comments yet. Start the discussion.