← News
/PLAYBOOK · 2026-08-01

CDN Cache Poisoning Defense: Cache Key and Forwarded-Header Hardening

Harden against Web Cache Poisoning: tighten cache keys, validate X-Forwarded-Host, lock down commercial CDN cache. Self-hosted Nginx and cloud CDN configs, verification commands, and emergency purge.

Scope

Site behind a CDN (Cloudflare, Alibaba Cloud CDN, Tencent Cloud CDN, or self-hosted Nginx reverse-proxy cache): attackers can use Web Cache Poisoning to inject malicious content into CDN cache so every visitor hits a poisoned page or script. Blast radius is the whole internet. Applies to any cache-accelerated site. Focus: unsafe cache keys that fold untrusted query params or request headers. Covers self-hosted Nginx cache and commercial CDN.

Prerequisites

Mechanism

CDN cache identity is the cache key: default is usually Host + URL path; responses are stored and fetched by that key. Two typical poisoning paths:

Defense: shrink the cache key to trusted variables only, and make origin ignore/validate untrusted headers.

Procedure

1. Tighten Nginx cache key (self-hosted)

`# /etc/nginx/conf.d/cache.conf
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=site_cache:10m max_size=10g inactive=60m use_temp_path=off;
`
`# Inside the site server block
server {
    listen 443 ssl;
    server_name helixcyber.ai;

    location / {
        proxy_cache site_cache;
        # Cache key keeps only request method + Host + normalized URI; exclude all query parameters and extra headers
        proxy_cache_key "$scheme$host$uri";
        # Restrict cache methods
        proxy_cache_methods GET HEAD;
        # Only cache selected extensions
        proxy_cache_valid 200 301 302 10m;
        proxy_set_header Host $host;
        # Do not forward untrusted headers: reset X-Forwarded-Host so it cannot be used to build URLs
        proxy_set_header X-Forwarded-Host $host;
        proxy_pass http://127.0.0.1:8080;
    }
}
`

Key point: `$uri` is the Nginx-normalized path (no raw query string). Combined with not caching params, this avoids "different params, same cache key". For APIs that must vary by param, whitelist explicitly:

`# Only include whitelisted parameters in the cache key
proxy_cache_key "$scheme$host$uri?$arg_page";
`

2. Validate/sanitize forwarded headers (origin)

Origin must trust forwarded headers only from CDN edge or internal proxies. Nginx example: map to restrict X-Forwarded-Host:

`# Keep only when the source is a trusted proxy CIDR; otherwise reset to Host
map $remote_addr $safe_fwd_host {
    default $host;
    192.168.1.0/24 $http_x_forwarded_host;
}

location / {
    proxy_set_header X-Forwarded-Host $safe_fwd_host;
}
`

3. Restrict cacheable responses (commercial CDN)

In Cloudflare Cache Rules / Alibaba Cloud CDN cache config:

4. Add cache-hit verification headers

`# Origin Nginx emits security response headers to assist CDN decisions
add_header X-Content-Type-Options "nosniff" always;
add_header Cache-Control "public, max-age=600" always;
# If the response varies per user, declare it explicitly with Vary so the CDN does not cache incorrectly
# add_header Vary "Accept-Encoding, X-Custom" always;
`

Verification

`# 1. syntax check and reload
sudo nginx -t && sudo systemctl reload nginx

# 2. Verify parameters do not pollute the cache key: both requests should return the same content and hit the same cache
curl -sI "https://helixcyber.ai/?debug=1" | grep -iE "x-cache|cache-control"
curl -sI "https://helixcyber.ai/" | grep -iE "x-cache|cache-control"

# 3. Verify X-Forwarded-Host poisoning is ineffective: after tampering, still returns the normal page
curl -s -H "X-Forwarded-Host: evil.example.com" "https://helixcyber.ai/" | head -c 300
# Expected: normal homepage content, without links related to evil.example.com

# 4. Check origin access logs and confirm parameterized requests did not trigger differentiated responses
grep "debug=1" /var/log/nginx/access.log | tail -n 5
`

FAQ

FAQ 1: Why isn't `proxy_cache_key` enough?

Cache key is only "where to store". If origin returns different content for `?debug=1`, CDN still writes that response under the same key. Do both: ① exclude params/untrusted headers from the key; ② normalize at origin (ignore irrelevant params, reset X-Forwarded-Host) so content is identical for a given key. Both required.

FAQ 2: Poisoning already happened. How to purge?

Do immediately: ① full cache purge/refresh in the CDN console (by URL or directory); ② fix origin endpoints that emit variant responses (param/header parsing); ③ apply the cache-key and forwarded-header hardening above; ④ recheck `Vary`/`Cache-Control`; confirm sensitive responses are not cached; ⑤ keep pre/post-attack logs (including X-Forwarded-Host, referer) for attribution and purge scope.

Takeaway

Two poisoning paths—cache key and forwarded headers. Configs for self-hosted Nginx and commercial CDN: cache key = scheme+host+uri, param whitelist, trusted forwarded-header checks, no cache for sensitive responses, plus verification commands. Cache poisoning is consistency between "the key CDN sees" and "what origin returns". Constrain both ends. Put this on the go-live checklist for any CDN-backed site; regression-test with the curl cases above.