Web Cache Poisoning: How Attackers Weaponize CDNs and Reverse Proxies

SCRs Team
February 17, 2026
14 min read
243 words
Share

What Is Web Cache Poisoning?

Web cache poisoning tricks a caching layer (CDN, reverse proxy, load balancer) into storing a malicious response and serving it to all subsequent visitors.

Normal flow:
  User → CDN Cache (cached /page) → 200 OK (clean page)

Poisoned flow:
  Attacker → Origin Server → 200 OK (malicious response injected via headers)
         ↓
  CDN Cache stores poisoned response
         ↓
  ALL subsequent visitors → CDN Cache → 200 OK (malicious page!)

Impact: One request from an attacker → XSS/defacement/phishing served to every visitor until cache expires.


How Cache Poisoning Works

The attack relies on unkeyed inputs — parts of the request that the cache ignores when generating the cache key, but the origin server uses in the response.

Cache Key vs. Full Request

Full Request:
  GET /page HTTP/1.1
  Host: target.com
  X-Forwarded-Host: evil.com     ← Unkeyed (not in cache key)
  Accept-Language: en              ← Unkeyed
  Cookie: session=abc123           ← Usually unkeyed

Cache Key (what CDN uses to match requests):
  GET target.com/page
  
The CDN sees the same "page" for all visitors, regardless of 
X-Forwarded-Host or Accept-Language headers.

Attack 1: X-Forwarded-Host Header Poisoning

Many frameworks use X-Forwarded-Host to generate absolute URLs for assets, links, and redirects.

# Attacker's request
GET / HTTP/1.1
Host: target.com
X-Forwarded-Host: evil.com

# Origin response (using X-Forwarded-Host for asset URLs)
<html>
  <script src="https://evil.com/assets/main.js"></script>
  <link rel="stylesheet" href="https://evil.com/assets/style.css">
</html>

# CDN caches this response for the cache key "GET target.com/"
# All visitors now load JavaScript from evil.com!

Defense

// ✅ Never use X-Forwarded-Host without validation
const ALLOWED_HOSTS = ['target.com', 'www.target.com'];

function getHost(req: Request): string {
  const forwarded = req.headers['x-forwarded-host'];
  if (forwarded && ALLOWED_HOSTS.includes(String(forwarded))) {
    return String(forwarded);
  }
  return req.headers.host || ALLOWED_HOSTS[0];
}

// ✅ Better: Use a fixed base URL from config
const BASE_URL = process.env.BASE_URL; // https://target.com

Attack 2: Unkeyed Header Reflection

# Find headers reflected in response
GET / HTTP/1.1
Host: target.com
X-Custom-Header: <script>alert(1)</script>

# If the response includes:
# <meta name="custom" content="<script>alert(1)</script>">
# And the CDN caches it — stored XSS for all visitors

Testing Methodology

# Step 1: Find unkeyed headers using Param Miner (Burp extension)
# Or manually test common headers:
X-Forwarded-Host
X-Forwarded-Scheme
X-Original-URL
X-Rewrite-URL
X-Forwarded-Proto
X-Host

# Step 2: Check if header value appears in response
# Step 3: Verify the response gets cached (check Age, X-Cache headers)
# Step 4: Confirm other users get the cached poisoned response

Attack 3: Fat GET / POST Body Poisoning

Some caching systems treat GET requests with a body the same as regular GETs.

# Cache key: GET /api/user (no body considered)
GET /api/user HTTP/1.1
Host: target.com
Content-Length: 30

{"role": "admin", "id": "1"}

# If the origin uses the body to customize the response,
# the cache stores the admin response for /api/user
# All users now see admin data!

Attack 4: Cache Key Normalization Exploits

# Some CDNs normalize URLs before generating cache keys:
# /page and /PAGE might share the same cache key
# /page and /page? might share the same cache key

# But the origin server might treat them differently,
# allowing you to poison the normalized version

Detection: Check Your Cache Headers

# Look for these response headers to understand caching behavior
curl -sI https://target.com | grep -i 'cache\|age\|x-cache\|cf-cache\|vary'

# Key headers:
# Cache-Control: max-age=3600        → Response is cached for 1 hour
# Age: 542                           → Response has been cached for 542 seconds
# X-Cache: HIT                       → Response served from cache
# Vary: Accept-Encoding              → Cache key includes Accept-Encoding
# CF-Cache-Status: DYNAMIC           → Cloudflare not caching (safe)

CDN-Specific Defenses

Cloudflare

# Cache Rules → Only cache static assets
# Page Rules → Cache Level: Bypass for dynamic pages
# Transform Rules → Strip unrecognized headers before origin

CloudFront

{
  "CacheBehavior": {
    "ForwardedValues": {
      "Headers": ["Host"],
      "QueryString": true
    },
    "CachePolicy": {
      "HeadersConfig": {
        "HeaderBehavior": "whitelist",
        "Headers": ["Host", "Accept-Encoding"]
      }
    }
  }
}

Cache Poisoning Prevention Checklist

  • Use fixed base URLs — never generate URLs from request headers
  • Strip unknown/custom headers at the CDN/proxy level
  • Ensure Vary header includes all inputs that affect the response
  • Don't cache responses that vary based on cookies or custom headers
  • Cache only static assets — bypass cache for dynamic/personalized content
  • Set Cache-Control: private, no-store for authenticated responses
  • Monitor cache HIT rates for anomalies
  • Test with tools like Param Miner to find unkeyed inputs
  • Use Surrogate-Control or CDN-Cache-Control for CDN-specific cache rules
Editorial standards

Published by SecureCodeReviews

This article is part of our original AI security and cybersecurity content library. We show publish and update dates, keep company and policy pages public, and update important guidance when material changes affect readers.

Named author: SCRs Team
Published: Feb 17, 2026
Update status: current publication version

Questions or corrections?

Review our editorial standards, learn more about the company, or contact us if a page needs clarification.

Cloud Assessment

Need a cloud security review before rollout?

We review IAM, network exposure, storage security, deployment posture, and the misconfigurations that usually get missed in fast-moving teams.

AWS, Azure, and GCP posture reviews
IAM, storage, network, and encryption validation
Clear findings with prioritized fixes for engineering teams

Talk to SecureCodeReviews

Get a scoped review path fast

Manual review
Actionable fixes
Fast turnaround
Security-focused

Advertisement