Wednesday, July 11, 2007

Exploiting reflected XSS vulnerabilities, where user input must come through HTTP Request Headers

Contents:


1.0 Introduction
2.0 The User_Agent Header
3.0 (Known) Firefox & Safari Request Header Injection (Sometimes)
4.0 Attacking Caching Proxies
5.0 References

1.0 Introduction


Ever since Adobe patched Flash player to stop attackers spoofing certain headers[1] such as Referer, User-Agent, etc, it has been considered impossible to exploit XSS vulnerabilities where the user input is taken from a request header, e.g. when a website prints out what User-Agent a user's browser is sending, without escaping it. With the exception of the Referer header which we can control enough to exploit XSS attacks through it.

I want to showcase several ways in which we can still exploit these vulnerabilities.

2.0 The User_Agent header


If you look at how the User-Agent header is accessed in certain languages (namely PHP/Perl/Ruby/ColdFusion), you will see that the User-Agent header is not referenced as it is sent over the wire:

PHP:
$_SERVER['HTTP_USER_AGENT']

Perl:
$ENV{'HTTP_USER_AGENT'}

Ruby:
@request.user_agent

ColdFusion:
CGI.HTTP_USER_AGENT

As you can see, all of these languages use an underscore(_) instead of a hyphen(-) when accessing the data, so if we use Flash to send a User_Agent header, like this:

class Attack {
  static function main(mc) {
    var req:LoadVars = new LoadVars();

    req.addRequestHeader("User_Agent", "<script>alert(1)</script>");
    req.send("http://localhost/XSS/server.php", "_self");
  }
}


(Can be easily compiled with mtasc)

So if any of those languages mentioned above insecurely print the User-Agent header, or any header with a hyphen in it, then no matter whether it is blocked or not, it can still be injected.

The three other languages which I checked where ASP, ASP.NET and Java, and this is how the variables are accessed:

ASP/ASP.NET:
Request.ServerVariables("HTTP_USER_AGENT")

Java:
request.getHeader("user-agent");

And while ASP/ASP.NET would seem to be vulnerable, it is a known fact to the MS developers[2] that headers with underscores cannot be accessed through the HTTP_ server variables, and so they have added more methods[2], which are not vulnerable.

Java does things neatly, and so it is not vulnerable.

Note: I made a mistake originally, and it seems that Perl apps are only vulnerable when testing through browsers other than IE.

Perl seems to use the last User_Agent or User-Agent header which you send it, and since the Flash plugin in IE appends our User_Agent header before IE's User-Agent header, Perl apps cannot be exploited when the user is Using IE.

3.0 (Known) Firefox & Safari Request Header Injection (Sometimes)


Stefano Di Paola published a paper[3], where he pointed out that IE7 and Firefox both facilitated request splitting when Digest authentication was used, Comcor also pointed out that Safari was vulnerable to the same issue.

IE7 was only vulnerable through the XMLHttpRequest object, which can only be invoked from the website which has Digest authentication, so it is useless to us in this case.

Furthermore request splitting is only useful when a user is behind a proxy (See Note at end of section), so if a user is not behind a proxy, then there is a point to spoofing headers rather than conducting a request splitting attack.

Anyway, what was not mentioned in the paper was that not only can the attack be invoked through an img tag, but it can also be invoked from an iframe tag, so here is a rather contrived PoC (based on Stefano's code):

digest.php:

<?php
  header('Set-Cookie: PHPSESSID=6555');

  if((int)intval($_COOKIE['PHPSESSID']) !== 6555){
    header('HTTP/1.0 401 Authorization Required');
    header('WWW-Authenticate: Digest realm="1@example.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"');
  } else {
    header("Set-Cookie: PHPSESSID=0");
  }

  print $_SERVER['HTTP_USER_AGENT'];
?>


attack.php:

<html>
<body>
  <iframe src="http://user%0aUser-Agent%3a%20%3Cscript%3Ealert%281%29%3C%2Fscript%3E%0aTest%3a%20:pp@localhost/XSS/digest.php"></iframe>
</body>
</html>


Note: Its not completely true that we can't do any request splitting without a proxy; we can still split requests and have a server with multiple vhosts interpret the second split response as a normal request for a vhost other than that which has the digest authentication. Thanks to Amit Klein for pointing this out.

4.0 Attacking Cache Proxies.


Warning: This attack is rather convoluted, and a bit impractical. *shrug*

With the advent of Anti-DNS Pinning being disclosed by Martin Johns[4] and improved on by Kanatoko Anvil[5][6][7], Kanatoko also found that Flash didn't Pin DNS[8], and who together found that Java's DNS record could be spoofed if used via LiveConnect[9].

This gives us an exceptionally powerful tool - an ability to create socket connections from a victim's computer.

So by utilising the low level socket abilities of Flash or Java we can create a socket connection to the user's caching proxy server if they have one. From there we can inject requests where we provide an XSS payload in the appropriate HTTP request header, which the proxy will sometimes cache, so when we redirect the user to that page they will be served the XSSed version.

This works because cache servers are often setup to cache html pages, and they are very sometimes setup so the only thing which is matches is the URL, rather than any headers or cookies.

Here is a step-by-step explanation:

1. First of all we need to know the IP address and port the users to access their proxy - sadly this part is rather browser specific.

On IE we can use Java[10][11] to detect the proxy settings in IE (I know nothing about Java, other than the fact that its possible, and there are known methods), or by using Javascript[11] in Firefox, by reading the Firefox settings network.proxy.http and network.proxy.http_port.

2. The next step is to open a socket to the proxy server, Anti-DNS Pinning attacks are already well documented in [4][5][6][7][8][9].

3. Send a request to the caching proxy with an XSS payload in the appropriate HTTP headers on the socket you have established.

4. Send the user to the cached page. This can be improved by using Flash to send a "Cache-Control: only-if-cached" header so that the proxy is more likely to serve the XSS-ed page.

Note: I don't have any code to test this, but from when I setup squid to cache html pages, creating a socket to the page and injecting into headers, then viewing the same page in the browser worked, and so I see no reason why this shouldn't. And I have personally seen live proxy servers setup to cache html pages, and where these attacks are possible, so while I'm not sure how common such setups are, they definitely exist.

5.0 References



[1] "Forging HTTP request headers with Flash", Amit Klein, July 2006
http://www.securityfocus.com/archive/1/441014

[2] "HOWTO: Retrieve Request Headers using ISAPI, ASP, and ASP.Net", David Wang, April 2006
http://blogs.msdn.com/david.wang/archive/2006/04/20/HOWTO-Retrieve-Request-Headers-using-ISAPI-ASP-and-ASP-Net.aspx

[3] "IE 7 and Firefox Browsers Digest Authentication Request Splitting", Stafano Di Paolo, April 2007
http://www.wisec.it/vulns.php?id=11

[4] "(somewhat) breaking the same-origin policy by undermining dns-pinning", Martin Johns, August 2006
http://shampoo.antville.org/stories/1451301/

[5] "Stealing Information Using Anti-DNS Pinning : Online Demonstration", Kanatoko Anvil, December 2006
http://www.jumperz.net/index.php?i=2&a=1&b=7

[6] "Re: DNS Spoofing/Pinning", Kanatoko Anvil, December 2006
http://sla.ckers.org/forum/read.php?6,4511,4539#msg-4539

[7] "Anti-DNS Pinning + Socket in FLASH", Kanatoko Anvil, February 2007
http://www.jumperz.net/index.php?i=2&a=3&b=3

[8] "Re: DNS Spoofing/Pinning", Kanatoko Anvil, February 2007
http://sla.ckers.org/forum/read.php?6,4511#msg-6253

[9] "Using Java in anti DNS-pinning attacks (Firefox and Opera)", Martin Johns & Kanatoko Anvil, February 2007
http://shampoo.antville.org/stories/1566124/

[10] "How to detect Proxy Settings for Internet Connection"
http://www.java-tips.org/java.net/how-to-detect-proxy-settings-for-internet-connection.html

[11] "RE: Auto-detecting proxy settings in a standalone Java app"
http://www.mail-archive.com/commons-httpclient-dev@jakarta.apache.org/msg07568.html

[12] "Read Firefox Settings (PoC)", Sergey Vzloman, May 2007
http://ha.ckers.org/blog/20070516/read-firefox-settings-poc/

5 comments:

Jordan said...

Very nice writeup and research. Who'd have thought that you'd actually be /less/ secure by following the spec (CGI 1.1)?!

The bottom line is that developers should continue to distrust all user input, but I'm sure this will be useful in exploiting folks who haven't taken that seriously enough and assumed headers from trusted clients could be trusted.

kuza55 said...

Exactly, and it will (hopefully) stop these bugs being put in the unexploitable basket.

Note: I made a mistake originally, and it seems that Perl apps are only vulnerable when testing through browsers other than IE.

Perl seems to use the last User_Agent or User-Agent header which you send it, and since the Flash plugin in IE appends our User_Agent header before IE's User-Agent header, Perl apps cannot be exploited when the user is Using IE.


Its not the most common occurance (IE users browsing sites written in Perl, err, sure, :p), so I don't think it really dents the research, but its disappointing I didn't find this before publishing it.

Kishor said...

That was a nice writeup.

For some reason, XMLHTTP object is allowing me to change User-Agent header on Firefox 2.0.0.4 as well as Opera 9.21

Is that expected?

kuza55 said...

@Kishor:

Yes it is expected, because the only header which is really sensitive enough that the site shouldn't be able to force the user to send it to itself is the Host header, because then it might not be getting sent to the same site, and would break some same-origin policy restrictions.

So yeah, as long as the cross-domain boundary is still intact, this is not an issue, and even when broken, there is really no use for this because there are worse things you can do.

The issue with what I posted above is that it can be done across domain boundaries.

Jhon Ther said...

Brilliant writeup! good research. I didn't know whole underscore / dash thing in headers. Interesting to see it.