Web Cache Deception
debt(d7/e5/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). Tools listed are owasp-zap, burpsuite, and cloudflare-analytics — these are specialist security scanners that require deliberate, targeted testing of authenticated endpoints with appended paths. The vulnerability is silent in normal development and CI pipelines; it surfaces only when someone actively probes CDN cache behaviour for personalised responses, making it closer to d7 than d5.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix says to audit what the CDN actually caches and ensure Cache-Control: no-store is set on all authenticated/personalised responses. In practice this requires reviewing and modifying HTTP headers across all authenticated routes or middleware layers, auditing CDN/reverse-proxy cache rules (often in separate infrastructure config), and validating the fix — spanning application code, middleware, and CDN configuration rather than a single-line swap.
Closest to 'persistent productivity tax' (b5). The applies_to scope is broad (all web contexts), and the fix requires ongoing discipline: every new authenticated endpoint must emit correct Cache-Control headers, and CDN cache rules must be continuously validated. This is a persistent operational tax on teams using CDNs — every feature touching personalised content must be reviewed for caching behaviour — but it doesn't reshape the entire system architecture.
Closest to 'serious trap' (t7). The misconception field explicitly states developers confuse web cache deception with web cache poisoning — two distinct attacks that are routinely conflated. Beyond that, the common_mistakes show developers assume CDN caching is controlled by response headers alone, not realising CDNs may cache based on file extension in the appended URL suffix regardless of Cache-Control. This contradicts reasonable assumptions about how caching is supposed to work, placing it firmly at t7.
Also Known As
TL;DR
Explanation
Web Cache Deception (discovered 2017) exploits misconfigured caches that serve any URL ending in .css, .js, or .png from cache without authentication. An attacker tricks a victim into visiting https://bank.com/account.php/fake.css — the server returns the real /account.php response (authenticated content) but the cache stores it publicly under the /fake.css variant. The attacker then fetches the same URL without authentication and receives the cached sensitive page. Mitigations: configure caches to respect Cache-Control: no-store headers, never cache responses based on file extension alone, and ensure authenticated responses carry appropriate Vary and Cache-Control headers.
Common Misconception
Why It Matters
Common Mistakes
- Cache keyed on URL path without considering that appended static-looking suffixes (/profile/style.css) are cached.
- Not setting Cache-Control: no-store on private authenticated responses.
- CDN or reverse proxy that caches based on file extension rather than response headers.
- Not testing cache behaviour of authenticated endpoints with appended paths.
Avoid When
- Do not cache responses based solely on file extension — attackers append .css or .js to URLs to trick caches.
- Never serve personalised content without explicit Cache-Control: private or no-store headers.
When To Use
- Set Cache-Control: no-store on all authenticated or personalised responses.
- Validate that CDN/reverse proxy cache rules are based on the full URL path including query strings, not just the file extension.
Code Examples
// Attack: GET /account/profile/nonexistent.css
// Server returns /account/profile (authenticated user data)
// CDN sees .css extension → caches as static asset
// Attacker fetches /account/profile/nonexistent.css → gets victim's profile
// Fix:
header('Cache-Control: no-store, private'); // On all authenticated responses
// Verify CDN respects Cache-Control headers and does not cache on extension
# Cache deception: attacker tricks CDN into caching a private page
# GET /account/settings/fake.css
# → PHP serves /account/settings (private) because it ignores .css
# → CDN caches it as a 'static asset'
# Prevention:
# 1. Set Cache-Control: no-store on all authenticated responses
header('Cache-Control: no-store, private');
# 2. nginx — only pass extensionless/php paths to PHP-FPM
location ~* \.(css|js|png|jpg|webp)\$ {
try_files $uri =404; # serve only real static files
}
# 3. Vary: Cookie — CDN won't cache when session cookie present
header('Vary: Cookie');