Eradicate HRS Once and for All

In the spirit of how to perform output filtering, the next in the series of removing semantic flaws from your web applications is how to remove HTTP Response Splitting/HTTP Request Smuggling/HTTP Header Injection. And I believe that this is going to be the most difficult of the series to write because of differences in standards and implementations.

If you do a search for any of those three, you'll find whitepapers on the first two, but the third is probably the most pervasive, easily-exploitable, and dangerous. However, all of them stem from the same vulnerabilities - first is a lack of input validation on data that ultimately makes it into response headers, and the second is a lack of output filtering on data going into response headers.

In most frameworks, you will have many different ways of applying headers - possibly one for sending a redirect, one for sending a cookie, and one for setting arbitrary headers - and there could be more. Each of these take some sort of value that you (the developer) are applying to a header. A redirect is just a 300 level response code with a Location header set to the new location. A cookie is just a Set-Cookie header with the cookie name and value.

HTTP Headers need to be URL encoded - this is different from HTML encoding. The characters that are "special" to HTTP are different from those that are "special" in HTML. Fortunately, there's MORE support in the frameworks (since it's lower level) for URL encoding than there is for HTML encoding. For example, in J2SE, there's the java.net.URLEncoder class, but there's not a J2SE encoder for HTML.

So the steps for output encoding this are similar to HTML:

  • Ensure that you've used to the degree possible a level of indirection (particularly with exit pages - those are often implemented with a redirect).
  • Perform any business rule input validation on the data to ensure that the data received is meaningful in the operation you'll be using it in.
  • Perform URL encoding - if at all possible use your framework's built-in mechanism. Server.URLEncode in ASP/ASP.NET, java.net.URLEncoder.encode() in Java, urlencode in PHP, CGI::escape in Ruby.
  • Always, always, always determine if there's a better way to implement what you're doing without using headers - particularly stuff going into cookies. Rather than set a cookie with the username, you should store the username in the session, and always take it from there (unless you're just using the cookie for auto-fill functionality after the user logs out). If you actually set a cookie with no expiration, it's very likely the same value can (and should) be put in the session instead.
The reason this one is so hard to write is twofold:
  • Particularly with my testing with J2EE, there are mixed results in actually trying to inject something. My testing has been limited, but the only place that any documentation states is the Cookie constructor throws InvalidArgumentException (according to API doc) if you put in some unescaped and invalid character (such as a newline). However, in HttpResponse methods (sendRedirect(), addHeader(), etc.) my experience has been that newlines (and carriage returns and linefeeds) get translated to 0x20. I can't find anything in the Servlet specification that states this as a requirement, so I don't know if it's common or only the way Tomcat handles it. I'll do more testing and let you know what I find from other vendors. (But I know from ethical hacking that I've seen HTTP injection in J2EE apps before).
  • If for some odd reason you can't find a method for URL encoding in your framework, it's actually not trivial to write. The HTTP RFC's are built from other RFC's (such as MIME, etc.), and so goes with headers. It's not just a matter of URL encoding so much as what needs to be specially handled. And then, it's not just what characters as it is which instances of those characters. For example, if you're sending a redirect, you don't want to URL encode the entire URL, otherwise, it's not a URL anymore - but you want to URL encode any parameters. So be very careful in dealing with this.
Hopefully this will get you started on writing safer headers. And really, if you can write fewer headers, by all means, do so.