Caching keeps the modern web fast, but it also introduces an attack surface where origin rules and edge assumptions drift apart. Web Cache Deception (WCD) takes advantage of that drift: an attacker convinces a cache to store content that should have remained private, then retrieves it before the victim notices. This playbook breaks down how caches make decisions, the primitives that enable WCD, and how red teamers attempt to route around CDNs and proxies.
Reminder: Everything here is for defense, testing, or responsible research. Never target systems you do not own or have permission to assess.
Cache Fundamentals
| Layer | Examples | Typical Scope | Trust Assumptions |
|---|---|---|---|
| Browser cache | Chrome, Firefox | Single user agent | Honours Cache-Control headers exactly |
| Edge cache / CDN | CloudFront, Akamai, Cloudflare | Shared across clients/regions | Derives cache key (host + path + headers). May ignore query strings by default |
| Reverse proxy | Nginx, Varnish, Envoy | Per origin POP | Usually trusts upstream app path parsing |
| Application cache | In-process, Redis | Internal | Relies on app to hash the right key |
When any layer builds a cache key differently than the origin expects, attackers gain room to trick it.
Anatomy of Web Cache Deception
flowchart LR
A[Attacker] -->|Craft request| B[CDN / Edge Cache]
B -->|Forward| C[Origin App]
C -->|Responds as private| B
B -->|Stores response| D[(Shared Cache)]
Attacker -.->|Later fetch| D -.->|Receives private content| A
- Attacker requests a victim-only resource (e.g.,
/account) but dresses the path to look static (/account.css/profile.js). - Origin still authenticates and returns private HTML because it resolves
/accountafter stripping suffixes. - CDN considers the path static (due to suffix) and caches it for everyone.
- Attacker replays the cache key and retrieves the stored sensitive response.
Core Deception Techniques
1. Path Confusion
GET /account.php/profile-picture.png HTTP/1.1
Host: target.com
- Origin behaviour: Normalises everything after
/account.php, serves authenticated dashboard. - Cache behaviour: Sees
.pngextension, treats response as static, caches it. - Detection tip: Compare
X-Cachebetween/fooand/foo.css/foo; mismatched TTLs indicate exploitable differences.
2. Query String Mismatch
Many CDNs strip query strings unless configured otherwise. Attackers append harmless parameters to differentiate origin routing while keeping the cache key identical.
Original: /profile?format=json # private, not cached
Deception: /profile;logout?static=1 # origin treats ';logout' as path param, CDN ignores3. Header Downgrade
- Some reverse proxies respect
X-Original-URLorX-Rewrite-URLwhile edge caches ignore them. - Attackers send
GET /static/logo.pngwithX-Original-URL: /billing, causing the origin to return billing data under a static cache key.
4. Vary Header Abuse
If the origin sets Vary: Cookie but the CDN strips it, private responses may be cached globally. Look for inconsistent Vary coverage in responses that contain personalised data.
5. 404/500 Caching
Misconfigured proxies cache error pages containing stack traces or session data. Trigger with GET /doesnotexist.css, note Cache-Control headers, then fetch again to confirm caching.
CDN and Proxy Bypass Techniques
| Technique | Goal | Notes |
|---|---|---|
| Origin pull guessing | Hit origin.target.com or backend-target.com to skip CDN entirely |
Use dig on CNAMEs or certificate transparency logs |
| Host header pivot | Send Host: origin through CDN to force it to proxy raw |
Some CDNs validate host; others forward blindly |
| X-Forwarded-Host injection | Override virtual host routing inside origin | Works when upstream app trusts X-Forwarded-* without allowlist |
| Double-encoding | %2e or %2f sequences bypass CDN path normalization |
Test /..%2fadmin vs origin behaviour |
| HTTP verb confusion | Some CDNs only cache GET/HEAD; POST might pass through | Send POST with _method=GET to see origin response |
| Cache-busting vs caching | Toggle between ?cb=rand (no cache) and path-suffix (force cache) |
Lets you verify which layer responded |
Practical Recon Workflow
# 1. Map cache keys with canned payloads
for suffix in '' '.css' '.js' '.png'; do
curl -s -D - "https://target.com/account${suffix}" -o /dev/null | grep -Ei 'x-cache|cache-control'
done
# 2. Probe CDN vs origin
hosts=("target.com" "origin.target.com")
for h in "${hosts[@]}"; do
curl -s -o /dev/null -w "%-20{http_code} %-10{size_download} %{remote_ip}\n" "https://$h/account.css"
done
# 3. Attempt header override
curl -s -D - \
-H "X-Original-URL: /billing" \
-H "X-Forwarded-Proto: https" \
"https://target.com/logo.png"
Exploitation Patterns
- Static suffix deception: Append
.css,.map,.jpgto dynamic routes. - Path Parameter Smuggling:
/dashboard;static=1/style.css(Tomcat/JBoss treat;as path param, CDN ignores). - Method override:
POST /cacheablewithX-HTTP-Method-Override: GET. - Response splitting: Poison cache headers using newline injection (rare but high impact).
- Edge-side includes (ESI): Abuse
<esi:include>fragments if the CDN merges authenticated content into shared templates.
Always verify legally: reproduce only inside authorised staging or bug bounty scopes, disclose responsibly, and provide remediation guidance.
Detection & Telemetry
- Delta diffing: Capture headers for
/privatewith and without deceptive suffixes. - Synthetic monitors: Run scheduled probes that request
/profile.css/random. Alert ifX-Cache: HIT. - Cache-key logging: Enable
cf-cache-status,X-Cache-Key, or custom logging on reverse proxies to observe what actually gets cached. - WAF correlation: Pair cache hits with authentication logs; if a cache hit lacks a matching login, investigate.
Defensive Playbook
Harden Cache Configuration
# Never cache responses that depend on cookies
types_hash_max_size 4096;
proxy_cache_bypass $http_cookie;
proxy_no_cache $http_cookie;
proxy_cache_key "$scheme$request_method$host$request_uri$http_cookie";
- Respect
Varyheaders end-to-end. - Default to no cache for authenticated paths; explicitly allow only safe assets.
- Strip ambiguous suffixes (e.g., enforce
.cssrouting to static directory only).
CDN Rules
- Enable cache key normalization: include query strings and critical headers.
- Create cache tags: mark
/account*asbypass. - Turn on Authenticated Origin Pulls so attackers cannot hit origin directly without presenting a client cert.
Application Guard Rails
- Serve private content with
Cache-Control: private, no-store, max-age=0. - Add
Content-Disposition: attachmentfor sensitive document downloads so browsers will not cache them accidentally. - Validate request paths strictly before invoking business logic.
- Ignore untrusted forwarding headers unless they originate from a known proxy IP range.
Monitoring & Response
# Cloudflare example: list cache purge events
curl -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE/cache/purge" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json"
# Akamai: check Property Manager version for cache key changes
akamai property-history --property target.com
- Purge suspicious cache keys immediately (
/login.css*). - Rotate session identifiers if you suspect private pages were cached.
- Instrument SIEM rules for repeated access to suffix-manipulated paths.
Reporting Guidance
When filing a bug bounty or internal report, include:
- Affected cache layer (e.g., CloudFront distribution ID).
- Exact cache key (method + host + path + query).
- Steps to reproduce with timestamps and
X-Cacheheaders. - Evidence that the response contained sensitive data (redact as needed).
- Mitigation recommendation: header fix, rule update, or configuration change.
Further Reading
- Akamai: Web Cache Deception Explained
- PortSwigger: Practical Web Cache Poisoning
- Cloudflare Docs: Controlling Cache Keys
- OWASP Testing Guide v5 - OTG-INPVAL-004
By PlaidNox Security Team
Revised Nov 2025