Saturday, February 23, 2008

HTTP Range & Request-Range Request Headers

For those that haven't heard of the Range header, here's a link: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2

For everyone who can't be bothered reading a link; essentially the Range header is what you use to ask a server for a part of a response, rather than the whole response; it's what makes resumeable downloads possible over http.

Anyway, another piece of research I presented at 24c3 is pretty simply, but rather useful:

Flash



If Flash were to allow us to send a Range header, then we would be able to get things sent to us completely out of context.

Until Apache 2.0 this header was only used when requesting static files, so it would not influence a typical XSS, however if we have an application such as vBulletin which protects against FindMimeFromData XSS attacks by searching the first 256 bytes for certain strings, then we can simply place our strings after the first 256 bytes and get Flash to send a header which looks like this:
Range: bytes=257-2048
To have unscanned data sent in the first 256 bytes, leading to XSS.

However since Apache 2.0 (and possibly in other webservers, but they're irrelevant to this post), the Range handling code is implemented as a filter; this means that it is applied to the output of every request, even if they are dynamic requests.

This means that if we were to have a normally unexploitable XSS condition where our use input was printed to the page inside an attribute with quotes either encoded or stripped, but all other metacharacters left intact, or an xss filter did not encode the html attributes you had at all like so:
<a href=“http://site.com/<script>alert(1)</script>”>link</a>

Then we could use the Range header to request only our unencoded portion which would result in XSS.

Now, why is this important since Flash has never let anyone send a Range header?

Well, while looking through the Apache source code I found this beautiful snippet:
if (!(range = apr_table_get(r->headers_in, "Range"))) {
range = apr_table_get(r->headers_in, "Request-Range");
}


Which essentially says that if you can't find a Range header, look for a Request-Range header, and untill the latest version of Flash (9,0,115,0), the Request-Range header was not blocked. (I had hoped this would be unpatched when I presented it, but you can't really hope for much when you sit on a bug for almost half a year...)

Firefox


Now the part I didn't present. In Firefox 3 Firefox implemented the new Cross-Site XMLHttpRequest which, as the name suggests, lets you make cross-site requests and read the responses.

There is some documentation here: http://developer.mozilla.org/en/docs/Cross-Site_XMLHttpRequest

The part of those specs which is relevant to this post is that you can allow Cross-Site XMLHttpRequests by including an XML preprocessing instruction; however you can't just XSS it onto the page as usual because it needs to be before any other data.

However, the XMLHttpRequest object allows you to append both Range and Request-Range headers. And by appending a Range header we can get our XSS-ed instruction to the start of the response, and read the response.

The limitations with this are fairly strict; as far as I can tell, you cannot add to the XHR policy cache with xml instructions, only with headers, and if you attempt to request multiple ranges, then the multi-part boundary which begins the response will be sent before the xml instruction, and so it will not be parsed, so you can only get the contents of the page that are after your XSS point. On the other hand I wasn't even able to get non-GET requests to work with server-side co-operation, so take these with a handful of salt.

On the up side, this does bypass the .NET RequestValidation mechanism since that does not flag on <? However, I doubt this will be very exploitable in many scenarios, though given the amount of .NET apps which are only protected by the RequestValidation mechanism; you're sure to find something.

3 comments:

Script Hacker said...

Nice!

Anonymous said...

Just a reply to FindMimeFromData():
http://msdn.microsoft.com/en-us/library/ms775147.aspx


The function will return the server content-type header if it doesn't recognize it. So the server might use the header "image/leave-me-alone" instead of "image/gif" and IE wouldn't change try to guess it.

Then, because it fails to find it, it will look at the extension - and (probably) correctly use image/gif.

I haven't tested it, but this is how I understand the documentation. It is an ugly hack anyway, but better this than checking first 256 bytes or copying the image...

Anyway, just an idea, thought I would share. :)

Nice blog!

Anonymous said...

I can't reproduce this bug in 6.0.2900.2180.xpsp_sp2_gdr - it might be fixed. Finally. :)

Best,

Andr3w