Wednesday 29 June 2011

XSS in confined spaces

It's always nice to see a manager or developer fully understand the severity of cross-site scripting (XSS). Displaying user-supplied input without sufficient encoding can have a serious impact on a web application – in particular, its users may become vulnerable to remote session hijacking, autocompleted passwords could end up being covertly siphoned off to the attacker, and most CSRF (cross-site request forgery) protection mechanisms can be bypassed.

However, those who do understand the risks of XSS may nonetheless dismiss the possibility of such nefarious attacks if the vulnerable input parameter is truncated to less than 30 characters or so. After all, how can you steal a password in less than 30 characters?

XSS in 10 characters (well, 27 really)

Many web application security testers will demonstrate an XSS vulnerability by injecting something like <script>alert(123)</script> into a webpage. This will cause a dialog box to pop up and display the message "123".

Unfortunately, that's the kind of benign demonstration that makes a manager think, "So what?"

Demonstrations are much more powerful if they show something bad happening, but how can that be done if we've already reached the maximum length (27 characters) of the vulnerable input parameter?

After the overhead of the script tags (of which there must be both an opening and closing one), there is only room to inject 10 characters of JavaScript. Believe it or not, that's enough to carry out any kind of cross-site scripting attack, including session hijacking, self-propagating JavaScript worms and CSRF exploits.

Bootstrapping larger XSS payloads

These 10 characters of JavaScript can effectively 'bootstrap' a much larger JavaScript payload from the window.name attribute. This is a special attribute which is persistent across domains. An attacker can exploit this behaviour by using any website under his control to store a large JavaScript payload in the window.name attribute, which is practically unlimited in size:

window.name = "var x=1; do_some_bad_stuff(); [...] ";

When the victim uses the same browser tab to view the vulnerable website, the payload will remain accessible via the window.name attribute. In practice, this is most likely to be exploited through the use of a hidden iframe, which sets the payload from the attacker's site, and then automatically redirects the iframed window to the vulnerable page on the target site.

Using the XSS vulnerability on the target site, the attacker's payload can be executed by injecting these 10 characters of JavaScript:

eval(name)

I'm not aware of an XSS vulnerability that can be exploited in fewer than 10 characters, so let me know if you find one! Note that the example above requires 27 characters if you include the script tags, although this can obviously be reduced if it is possible to inject user-supplied input directly into an existing block of JavaScript (this is not unheard of!).

Chained XSS

If a web application displays user-supplied input without sufficient encoding, it would generally remain safe from cross-site scripting attacks if the maximum length of the input is restricted even further than in the previous examples.

However, if there are several vulnerable inputs on the same page, these can be combined to construct a valid piece of executable JavaScript which would not have fitted into a single input parameter.

This can be demonstrated by the following vulnerable HTML template, which fails to encode the name and telephone numbers after a user submits some feedback:

<p>
 [    $name     ] - thanks for submitting your comments.
 Your feedback is important to us. As requested, we will
 contact you about this issue as soon as possible, using
 the contact details provided below:
</p>
<ul>
 <li>Landline: [  $tel1   ]</li>
 <li>  Mobile: [  $tel2   ]</li>
</ul>

The user name is limited to 16 characters in length, while both of the telephone numbers are limited to 12. Although each individual field is too short to allow a practical XSS attack, the vulnerable fields can effectively be chained together by submitting the following values:

 $name: <script>alert(/*
 $tel1: */'XSS'/*
 $tel2: */);</script>

This will result in the web server returning the following HTML to the client. The JavaScript comments chain the vulnerable fields together, allowing the injected script to be parsed correctly and executed by the browser:

<p>
 <script>alert(/* - thanks for submitting your comments.
 Your feedback is important to us. As requested, we will
 contact you about this issue as soon as possible, using
 the contact details provided below:
</p>
<ul>
 <li>Landline: */'XSS'/*   </li>
 <li>  Mobile: */)</script></li>
</ul>

Big or small, gotta fix 'em all.