Fixing Denial of Service vulnerabilities in Node.js
As part of the security release of the 27th of November 2018, we fixed several Denial of Service vulnerabilities related to headers processing. You should upgrade your Node.js versions to v6.15.0, v8.14.0, v10.14.0, v11.3.0. This blog post is an in-depth explanation on how those attacks were fixed.
A long-time advice in the Node.js community is to deploy Node.js protected by a full a web server, like NGINX. As of the 27th of November, we fixed some critical Denial of Service vulnerabilities (Uncontrolled Resource Consumption – CWE-400) that makes Node.js safer when deployed without a web server.
Two headers-related vulnerabilities are fixed in that security release:
- the maximum size of HTTP/1 headers is now 8KB (before it was 80KB); CVE: CVE-2018-12121
- the maximum time that the HTTP/1 headers could be received is now limited to 40 seconds by default, a.k.a. Slowris; CVE: CVE-2018-12122.
CVE-2018-12121: Maximum size of headers attack
Node.js uses http_parser to parse the incoming HTTP/1 request. By default, the maximum amount of headers that could be sent prior to the fix was 80 KB, while most web servers limit this to 6 or 8 KB. This difference could be easily exploited to create a memory exhaustion attack on a Node.js server by creating a large amount of HTTP requests, and never terminate the headers block with ‘\r\n’.
Note that there is a default timeout for inactivity set at 2 minutes, but this is still a long amount of time: as an example, with 25.000 pending requests, it will allocate 1.9 GB of memory on the heap (200MB with the fix). This is normally not a problem, but on specific conditions, it was possible to cause the Node.js process to crash due to memory exhaustion. This type of attack requires a huge amount of bandwidth to pull off.
There is no security revert flag for this check, i.e. it cannot be disabled or configured.
CVE-2018-12122: Prevent Slowris-style attack on HTTP headers
This is a Node.js-flavor of a common attack to web servers, known as Slowris. In this attack, a client sends data at the slowest possible throughput to not trigger an inactivity timeout on the HTTP/1 server (2 minutes in Node.js by default).
A Slowris attack could happen for both the headers and the body. Parsing the body is left to the ecosystem, however, Node.js should not be vulnerable to potential slowris attacks for parsing the headers themselves.
This is fixed by a new server setting server.headersTimeout, that sets the maximum amount of time to receive the headers to 40 seconds by default. This could be tweaked according to your application requirements.
The real challenge to fix this attack was to not compromise Node.js throughput and speed, as initial prototypes had a significant drop in throughput, up to 50%. The reason for this drop was the use of a timer to control when to expire the connection. Instead, we settled on a different approach: whenever a data chunk is received, we are checking if the maximum amount of time for parsing the headers has elapsed. Generating this timestamp is already cached for each second to generate the HTTP response timestamp, so it does not introduce a new bottleneck for high-speed servers.
We still recommend deploying Node.js behind a Web server to protect against Denial of Service attacks. However, Node.js just become a bit safer if you decide not to do so. Remember to upgrade your Node.js deployments to v6.15.0, v8.14.0, v10.14.0, v11.3.0.
You may find this video helpful where I talk about choosing which version of Node.js you should use
Image: Sylwia Bartyzel