# React Router and the Remix’ed path

React Router Express adapter concatenates unsanitized X-Forwarded-Host port into resolvedHost, letting attackers rewrite the request path and poison caches via Remix _data parameter.

- Payout: $4,500
- Severity: high
- Program: React Router
- Disclosed: 2025-04-01
- CVE: CVE-2025-31137
- Write-up quality: 8/10
- Original: https://zhero-web-sec.github.io/research-and-things/react-router-and-the-remixed-path
- Library page: https://bugbountylibrary.com/r/blog%3Azhero-web-sec-github-io-research-and-things-react-router-and-the-remixed-path
- Tags: area:web domain:web impact:dos kind:research platform:research sev:high tech:nodejs technique:chained technique:source-review vuln:cache-poisoning vuln:dos vuln:host-header

**Target:** Express adapter host resolution / Remix data requests
**Root cause:** The Express adapter splits X-Forwarded-Host on ':' and concatenates the post-colon value (treated as port) directly onto req.hostname without sanitization, then builds a URL with req.originalUrl, allowing arbitrary path injection.
**Steps:**
1. Send request with `X-Forwarded-Host: anything:/ssr` so the port value becomes `/ssr`
2. Adapter builds `http://localhost/ssr/` from protocol + hostname+port + originalUrl
3. Chain with Remix: `X-Forwarded-Host: x:/ssr?_data=routes/ssr&` to force a 200 JSON data response on any route
4. If URL params are excluded from cache-key, poison cache so normal page requests receive the JSON object
5. For WAF bypass, split SQLi/XSS payload between header and URL path, using `/* */` to comment out the trailing slash
**How found:** Source review of Remix led to the _data parameter behavior; then examining React Router's Express adapter revealed the port value from X-Forwarded-Host was concatenated unsanitized into the resolved URL.
**Exploitation:** Attacker sends a request with crafted X-Forwarded-Host containing a path after the colon; the adapter resolves a different internal path than the cache-key path, so the poisoned response (JSON data, 404, or alternate endpoint) is cached and served to subsequent users, causing CPDoS. Also used to bypass WAFs by splitting payloads across header and URL.
**Escalation:** Chaining with Remix _data parameter removes the cache-key restriction, making CPDoS broadly exploitable; WAF bypass enables reflected XSS (escalatable to stored XSS via cache) and SQLi.
**Insight:** Framework adapters that parse X-Forwarded-Host for a 'port' often fail to validate that the value is numeric — try injecting a path after the colon to rewrite routing while keeping the cache-key path intact, enabling cache poisoning and WAF bypass.

## Proof of concept (copied exactly from the original report)

**X-Forwarded-Host port path injection payload**

```
Since the part before the column character is not used, we omit it and place the desired path directly. This will create the following URL :
http: (req.protocol) + localhost/ssr (req.Hostname + port) + / (req.originalUrl)
This results in http://localhost/ssr/ (without modifying the path), allowing the bypass of certain path-based mechanisms.
```

**Chained CPDoS cache poisoning request**

```
We specify a path using a loader directly after the column character : (to get a 200 response), /ssr in our case, then we add the URL parameter ?_data as well as the corresponding routeId as explained previously. Note that we end the payload with the character (&) so that the slash of the current path (/) doesn’t distort the value, the latter being concatenated after the Host + supposed Port.
```

**WAF bypass SQLi payload split**

```
In the following example, we assume that a firewall checks both the URL and the value of the headers. We bypass it by splitting the payload — in this case, for an SQLi — into two parts, taking advantage of the fact that the part in the URL is the last to be concatenated (as explained earlier) to form the final URL, and therefore has no impact on routing.
```

