HTTP Request Smuggling in NodeJS
Back in August, we were asked to develop POCs for a DNS rebinding attack and several HTTP request smuggling vulnerabilities listed in the July 2022 NodeJS security blog.
Our research into these bugs resulted in the discovery of a new HTTP request smuggling vulnerability affecting all versions of the NodeJS 18.x, 16.x, and 14.x releases lines.
Details on this vulnerability are available in the Sept 2022 NodeJS security blog.
HTTP Request Smuggling
HTTP request smuggling is a technique for manipulating how HTTP servers process HTTP requests.
The technique applies to situations where a front-end server receives an HTTP request and forwards it to one or more back-end servers. Normally, a client sends a request to the front-end server and is unable to interact with the back-end directly. However, when the front-end is vulnerable to request smuggling it will forward malformed HTTP requests to the back-end, which may process the request in an unsafe manner, interpreting it as two or more distinct requests originating from the front-end.
The technique involves changing the
Transfer-Encoding headers in an HTTP request such that the front-and-backend servers disagree on how and where an HTTP request is delimited.
This has serious security implications, as HTTP request smuggling attacks may allow an attacker to bypass security controls on the target server.
The NodeJS standard library includes an HTTP module aptly named
http. This module includes an HTTP parsing library named
llhttp. It does things like parse headers in an HTTP request, and identify where contiguous HTTP requests within a TCP packet are delimited.
It is widely used, as NodeJS accounts for approximately 2.1% of all known web servers, or roughly a few hundred thousand devices.
The latest vulnerability, CVE-2022-35256, builds on the findings of the July security release.
llhttp parser in the NodeJS does not correctly handle header fields that aren’t terminated with
This vulnerability relates to the 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 most other headers such as
Content-Length). This malformed request should be rejected by the HTTP server. If it is not rejected, it may be used for HTTP request smuggling.
Let's look at some examples.
The following request is rejected because the
X-header is not
CLRF terminated, and therefore may not be properly parsed.
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
The server accepts the request when the X-header doesn't contain a value and isn't CLRF terminated. We can use this to hide a second HTTP request in the body of this encapsulated request and it will be interpreted as a second request originating from the front-end server. Not good.
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
What's interesting is that we can do this with most headers, including
Transfer-Encoding shown below.
printf "POST / HTTP/1.1\r\n"\ "Host: localhost\r\n"\ "X:\nTransfer-Encoding: chunked\r\n"\ "\r\n"\ "1"\ "A"\ "0"\ "\r\n" | nc localhost 5000
This vulnerability affects all versions of the NodeJS 18.x, 16.x, and 14.x releases lines.
To prevent exploitation NodeJS currently recommends upgrading to the latest version of their release line.