Learn how combining X-Forwarded-Host
and X-Original-URL
vulnerabilities can poison web caches, delivering persistent XSS to unsuspecting users.
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
Summary of the Vulnerability
This scenario demonstrates a web cache poisoning attack that relies on chaining two separate weaknesses: the X-Forwarded-Host
and the X-Original-URL
headers. Normally, web cache poisoning occurs when an attacker can manipulate the server's response in a way that gets stored in a caching layer, making the poisoned response delivered to other users.
In this lab, the server behavior creates a unique condition. The application fails to properly sanitize the X-Forwarded-Host
header and also mishandles routing when X-Original-URL
is supplied. Individually, neither issue alone results in a successful exploit. But when combined, they allow the attacker to modify cached responses in such a way that malicious JavaScript—alert(document.cookie)
in this lab, executes in the victim's browser once the poisoned page is retrieved from cache.
Steps to Reproduce & Proof of Concept (PoC)
① Open the lab and observe the site's homepage requests in Burp HTTP History.

- Notice the homepage
GET / HTTP/2
always requests two resources:
1. ../translations.js
2. ../translations.json


② Start the attack workflow:
- Make sure your Param Miner is activated.

- Send the homepage request (
GET /
) to Repeater. - Right-click the request → Extensions → Param Miner → Guess headers.

- Wait a few seconds for Param Miner to test likely headers.
③ Param Miner may surface manipulated headers such as:
1. X-Forwarded-Host
2. X-Original-URL


If Param Miner causes issues for you, disable/uncheck it. Since in some cases, my attack doesn't work if this extension still active, so I prefer disable it after finished scanning.
④ Test X-Forwarded-Host
manually:
- Add the header to the homepage request in Repeater:
X-Forwarded-Host: evil.com
- Send the request and inspect the response. If the response reflects your domain and the
Host
value is changed, the application is echoing the header — a promising sign that you can control how the server constructs responses.

⑤ Identify language switch behavior:
- Change the site language in the UI (English → Spanish).


- Observe the language request in HTTP history:
GET /setlang/es?
→ redirected to /?localized=1
Cookie: lang=es
- The
/setlang
flow setslang=es
and the redirect lands on/?localized=1
.

⑥ Prepare the Exploit Server to host a malicious translations file:
- On the Exploit Server, replicate the path used by the site:
/resources/json/translations.json
- Ensure the server responds with the malicious JSON and add the header:
Access-Control-Allow-Origin: *
This bypasses CORS when the page fetches resources from your Exploit Server.
- Place your payload where the page's UI pulls the translated string (for example the
"View Details"
string) so that when the language changes the DOM is controlled via/resources/js/translations.js
.

⑦ Reflect your Exploit Server using X-Forwarded-Host
:
- Change
X-Forwarded-Host
to your Exploit Server hostname and send the homepage request repeatedly until the response reflects your Exploit Server domain.

⑧ Test in-browser rendering:
- In Repeater, right-click the response → Show response in browser.
- If the payload executes (e.g.,
alert(1)
), you've successfully reflected the malicious resource.

⑨ Use X-Original-URL
to force a cacheable redirect:
X-Original-URL
can change the request path; try variations to trigger a redirect that becomes cacheable. Example attempts:
X-Original-Url: /setlang/es
X-Original-Url: \setlang\es
X-Original-Url: /setlang\es

- In my testing,
X-Original-Url: /setlang\es
produced a302 Found
response andX-Cache: hit
. The server normalized backslashes to forward slashes and treated the response as cacheable.

⑩ Construct the poisoning sequence (two-step chain):
- Step A (poison reflective content): Send
GET /?localized=1
with:
X-Forwarded-Host: <your-exploit-server>
This causes the localized response to reference your Exploit Server resource (the translations file with the payload).
- Step B (cache the redirected homepage): Immediately send repeated
GET /
requests with:
X-Original-URL: /setlang\es
These requests trigger a redirect that the caching layer stores (X-Cache: hit
), effectively poisoning the cached /?localized=1
content.


⑪ Verify exploitation:
- After performing the sequence multiple times, open the homepage (with language defaulted to English). If poisoning succeeded, the page will load the cached malicious translations and execute the payload (e.g., an alert showing
document.cookie
). - Repeat refreshing and check browser behavior; once the popup triggers for a regular visitor, the lab is solved.
⑫ Finalize and store:
- Update the Exploit Server payload to the final payload you want to test; click Store on the Exploit Server UI.

- Re-run the poisoning sequence if needed until the exploit reliably triggers for an English-language visitor.

- Confirm the lab is solved.

Impact
In production environments, this kind of chained cache poisoning vulnerability could be highly damaging. If an attacker manages to poison a cached response with malicious content, every subsequent user who loads that cached page will unknowingly execute the attacker's payload.
Potential consequences include Persistent cross-site scripting (XSS) — Since the payload remains in the cache until eviction, all visitors receive the malicious script.
📢 Enjoyed this post? Stay connected! If you found this article helpful or insightful, consider following me for more:
- 📖 Medium: bashoverflow.medium.com
- 🐦 Twitter / X: @_havij
- </> Github: havij13
🙏Your support is appreciated.