DEV Community

Cover image for What Is Web Cache Deception? How Can an Attacker Make a Cache Store Private Data?
Aditya Sharma
Aditya Sharma

Posted on

What Is Web Cache Deception? How Can an Attacker Make a Cache Store Private Data?

A user authenticates. They request a page that only they should see. The application checks their session, confirms they are authorized, and returns their private data. No authorization check was skipped. No SQL was injected. The application behaved correctly.

But somewhere between the user's browser and the origin server, a cache stored that response. Later, someone else requested the same cache key. The cache returned the previous user's private page.

The application didn't leak the data. The cache did.

This is the core question: how can a system designed to make websites faster accidentally become a place where private data is stored and served to the wrong person?


How Web Caches Work

When a user requests a web page, the request doesn't always travel directly to the origin server. Most production applications sit behind one or more intermediaries: CDNs, reverse proxies, load balancers. These components can serve responses from a local store rather than forwarding every request upstream.

Browser
  ↓
CDN / Reverse Proxy / Cache
  ↓
Origin Server
  ↓
Application
Enter fullscreen mode Exit fullscreen mode

A cache stores a response associated with a cache key, typically derived from the request URL and sometimes other request attributes. When a subsequent request arrives with a matching key, the cache can return the stored response without contacting the origin. This reduces latency and origin load.

Caching is a performance mechanism. The security problem arises when a cache stores a response that should not have been stored, or serves it to a request that should not receive it.


The Interpretation Mismatch

Modern web applications often route requests flexibly. A path like /account/profile might be handled by the application's routing logic, which checks the session cookie, fetches the relevant data, and returns a personalized response. The URL is not a path to a file. It is a route to application behavior.

Caches, however, often make cacheability decisions using different signals: URL path patterns, file extensions, explicit cache-control headers, or cache rules configured by an administrator. Those rules may or may not agree with what the application actually does.

The vulnerability class called web cache deception exploits the gap between these two interpretations.

Consider:

/account/profile
Enter fullscreen mode Exit fullscreen mode

The application routes this to the authenticated profile handler and returns private content.

Now consider:

/account/profile/style.css
Enter fullscreen mode Exit fullscreen mode

Whether this URL reaches the same handler depends entirely on how the application's routing is configured. Many frameworks will match the most specific route and fall back to the parent route when nothing more specific is found. Some serve the same profile content regardless of a trailing path segment they don't recognize. Others return 404.

If the application returns the same private profile response for /account/profile/style.css because its routing treats the unknown suffix as a parameter or falls through to the profile handler, then the origin's behavior is unchanged. The user gets their private data.

But the cache may see a .css extension and classify the response as a static resource, eligible for caching. The origin said "here is private user data." The cache heard "here is a stylesheet. I'll remember this."

Origin interpretation:   /account/profile/style.css → private profile data
Cache interpretation:    /account/profile/style.css → static CSS → cacheable
Enter fullscreen mode Exit fullscreen mode

Same URL. Different meanings to different components.


The Attack Sequence

The conceptual attack flow has several steps:

Victim authenticates
    ↓
Victim visits deceptive URL
    ↓
Origin returns private response (correctly authorized)
    ↓
Cache stores response under that URL's cache key
    ↓
Attacker requests the same URL
    ↓
Cache returns victim's private response
Enter fullscreen mode Exit fullscreen mode

For this to succeed, specific conditions need to align. The application must return the same private content for the crafted URL as for the original one. The cache must consider the crafted URL cacheable when it would not cache the original URL. The cache must not include session cookies or other user-specific attributes in its cache key for that request. And the response headers must not instruct the cache to treat the response as private.

These conditions are not guaranteed to exist. Whether a given system is vulnerable depends on the application's routing, the cache's configuration, the response headers, and how those components interact. Cache deception is a configuration and design problem, not an inherent flaw in any particular technology.


Cache Keys, Cacheability, and Why Authentication Does Not Save the Victim

To understand why this matters at a deeper level, it helps to understand how caches make decisions.

A cache key identifies which stored response to return for a given request. The key is often the URL, but many caches include or exclude other request attributes based on the Vary response header or configuration rules. If a session cookie is not included in the cache key, two requests with different sessions but the same URL may receive the same cached response.

Cacheability is a separate question. A cache determines whether a response can be stored based on the HTTP method, response status code, Cache-Control directives, and sometimes configured rules that override headers.

The dangerous scenario is when the cache key does not include user identity, the cache considers the response cacheable based on the URL pattern, and the application returns user-specific content for that URL.

It is important to be precise about authentication here. Cache deception does not bypass the application's authentication. The origin correctly authenticates the victim and correctly generates their private response. The authorization logic works. The problem occurs afterward: the cache observes a response already authorized by the origin and stores it, then serves that stored response to the next matching request without involving the application at all.

"The application authorized this response for this user."

vs.

"The cache should have stored this response for anyone who asks."
Enter fullscreen mode Exit fullscreen mode

These are different questions. The application answered the first correctly. The cache answered the second incorrectly.


HTTP Headers and Cache Control

HTTP provides mechanisms for controlling caching behavior through the Cache-Control response header.

Cache-Control: private tells shared caches not to store the response. Cache-Control: no-store tells caches not to store it at all. The Vary header specifies which request headers should differentiate cached responses: Vary: Cookie tells the cache to treat requests with different cookies as separate cache entries. RFC 7234 also specifies that caches should not store responses to requests carrying an Authorization header unless the response explicitly permits it.

These mechanisms work when correctly implemented and when the cache actually respects them. CDN cache rules that override response headers, misconfigured cache exceptions, or rules that prioritize URL matching over response headers can undermine what the application intends. The headers are necessary but not sufficient on their own.


Cache Deception vs. Cache Poisoning

These two vulnerability classes involve caches behaving in unintended ways, but the direction of the problem is different.

Cache deception: A victim's private, authorized response gets stored under a cacheable-looking URL. A subsequent request retrieves that private response from the cache.

Cache poisoning: An attacker causes a malicious or undesirable response to be stored in the cache, so legitimate users receive it.

In cache deception, the attacker benefits from something the victim received. In cache poisoning, the attacker puts something into the cache that other users receive. The mechanism involves caches in both cases, but the goal and the exploit structure are different.

Normal caching is neither: a public response is stored and reused, which is exactly what was intended.


Why Frameworks and CDNs Do Not Automatically Solve This

Modern frameworks often provide sensible defaults for authenticated routes. Many CDNs allow fine-grained cache rules. But security depends on the entire configuration working correctly together.

A framework that sets Cache-Control: private on authenticated responses does not help if a CDN rule overrides cache headers for URLs ending in .css. An application that routes unknown suffixes to dynamic handlers does not help if it does not set appropriate headers for those responses. A CDN configured to cache static assets does not distinguish between a real stylesheet and a URL that merely resembles one if no other signal is present.

The interaction between application routing, response headers, and cache configuration determines actual behavior. Assuming any single layer handles this correctly without testing the combination is where security gaps appear.


Defenses

Set appropriate Cache-Control headers on private responses. Cache-Control: no-store or Cache-Control: private tells shared caches not to store the response. This is the most direct defense and must be present on every response carrying user-specific content.

Ensure cache rules and application routing agree. If the cache caches all URLs with .css extensions, the application should not serve private content for those URLs.

Do not treat file extension alone as evidence of cacheability. URL pattern-based cache decisions without consulting response headers can override application intent.

Test URL normalization and routing differences. Check whether the application serves the same content for variations of a private URL: unknown path segments, trailing content, encoded characters, and unusual extensions.

Include user-identifying attributes in the cache key where appropriate. If personalized content must be cached, the cache key must reflect the attributes that differentiate responses between users.

Inspect actual CDN and reverse proxy behavior. Test the full request path under production-like conditions. Configuration and runtime behavior can differ.

Include cache behavior in security testing. Testing the application in isolation does not reveal how caches interact with it.


The Deeper Lesson

Authorization is not the only layer that determines whether a response reaches the right person.

An application can correctly authenticate a user, correctly generate a private response, and correctly close the connection. If the response was stored by a cache in between, the application's authorization decision no longer governs what happens next.

The security boundary for a web response includes every component that stores, transforms, routes, or reuses that response. A cache is not passive. It makes decisions, and those decisions can diverge from what the application intended.

Web cache deception exists in that gap. The application authorized a response. The cache decided to share it.

Top comments (0)