Anatomy of an XSS exploit

Last week I promised to follow up on a few XSS bugs that I found in WordPress. The vulnerabilities are fixed in WordPress 2.0.3, even though the release notes do not mention their existence. I think there are a number of useful lessons that can be drawn from them, so in this post I will describe some more details.

The goal of a classic XSS exploit is to run arbitrary Javascript, in the context of a another webpage, which retrieves the user’s cookies. With WordPress I will concentrate on the comment management interface. Here, the deletion button has a Javascript onclick event handler to display a confirmation dialog, which includes the comment author’s name. If malicious input can break out of the dialog box text, then when an administrator activates the button, the attacker’s Javascript is run, allowing access to the admin user’s cookies. I found two classes of bugs which allowed me to do this.

The first was not particularly interesting but still was effective. The confirmation dialog code occurred in many places, but one had simply omitted any input sanitisation on author names. A new HTML tag (e.g. <script>) could not be added because < and > were escaped as &lt; and &gt;, but the attribute could be closed, with double quotes, and a new one added. In my test I used an onmouseover event handler to execute the malicious Javascript if the admin hovered over the delete button. The failure comes from not properly treating all external input as malicious, but without any central sanitisation, catching every case is difficult.

The second bug was more interesting, and due to a systematic misunderstanding of how browsers interpret escape codes, rather than a lapse of attention. The issue is quite subtle, so I could imagine similar bugs cropping up in other applications, and it was present in several places in WordPress. The text of the confirmation dialog box is stored as a Javascript string literal. Clearly single quotes must be escaped, as the Javascript string delimiter, and PHP helpfully provides htmlspecialchars() which will replace them with &#039;. WordPress uses wp_specialchars() which in this respect behaves the same way.

However this isn’t enough. The browser’s HTML decoder will replace &#039; with a single quote before passing it to the Javascript interpreter. Thus an attacker can close the string literal and insert new Javascript. One place the vulnerability exists is triggered on deleting a comment from the moderation queue. Defending against this attack is difficult because of the two layers of decoding. Any character which a browser could translate into a something the Javascript interpreter might consider a single quote must be escaped, so brings in HTML entity syntax and potentially even complex Unicode normalisation algorithms.

What makes XSS bugs in WordPress particularly troublesome is the great power that an admin user cookie confers. My proof of concept code, written with the help of Richard Clayton. used the theme editor to escalate the XSS to arbitrary PHP injection, taking over the web server account. Furthermore, the payload activates automatically, because the inserted Javascript sends the cookie to a CGI script running elsewhere, which logs in and inserts the PHP.

Within seconds of the unlucky admin deleting what appears to be a spam trackback the server is 0wned. Note that to attack the webserver I needed another webserver so by combining the two components you can create a self replicating exploit. There has been a PHP worm before, but I don’t know of any XSS based ones. It would require user interaction, but the social engineering technique, of requiring a victim to delete spam, is perfect. Google could be used to find new blogs to infect, but this introduces a single point of failure. Instead the blog roll feature permits decentralised propagation.

So what can be done to mitigate against such attacks? I think the double-unescaping problem is a further indictment against the technique of removing invalid input. Instead, all input should be considered evil until proven otherwise. Rather than escaping suspect characters, a safer option is to specify what is valid and anything deviating from this is rejected. Or if possible, don’t insert user input into HTML at all, as WordPress have started to do. WordPress could have also enabled HttpOnly cookies, which would have protected users running Microsoft browsers, but not Mozilla/Firefox.

However even with careful design XSS vulnerabilities seem to be inevitable, so steps should be taken to mitigate the damage. In the case of WordPress the theme editor allows XSS to be escalated into arbitrary PHP injection. In my opinion requiring an admin to re-enter the password before editing files would improve resilience against XSS, but cause little interference.

Browsers could also help, for example by formally describing safe input characteristics. In the longer term a Javascript equivalent of prepared statements could give similar protection against double-unescaping as they currently do for SQL injection vulnerabilities.

Finally, I you haven’t already upgraded to WordPress 2.0.3 then now would be a good time to do it. If for some reason you can’t, then disabling comments, trackbacks and pingbacks would be an appropriate stop-gap measure.

A description of the second WordPress vulnerability I found will follow next week

7 thoughts on “Anatomy of an XSS exploit

  1. There has been a PHP worm before, but I don’t know of any XSS based ones.

    Just a few hours ago, F-secure blogged about a Javascript worm. Although there aren’t many details, it looks like it’s exploiting an XSS bug in Yahoo Mail. This also reminded me of the MySpace worm.

    I still am not aware of any worm which runs code both on the server and client, which would be the likely operation of my hypothesised WordPress XSS one.

  2. @Dave Armstrong

    Thanks for the pointer.

    I do know of some XSS worms (see comment 1), but the one I suggested is slightly different. It uses XSS as the infection vector, but propagation and payload code are run on the server. This reduces applicability to software like WordPress which allows XSS to server compromise escalation, but the payload can do more (e.g. botnet creation).

    A pure XSS worm might be possible with WordPress, but the two limitations we found were the 255 character limit on author name, and the variety of ways themes render the blogroll. We couldn’t see an easy way to find blogs to attack next, based only on the DOM tree in ~240 bytes of Javascript. However, if you can run PHP, the list can be trivially sucked from the database.

  3. Hi Steven,

    so you’re saying any PHP code which does this:

    $clean = array();
    $html = array();

    $clean['userinput'] = trim(strip_tags($_POST['userinput']));
    if ( strlen($clean['userinput']) < 3 )
    $html['warning'] = htmlspecialchars($lang['errMsgTooShort'] . $clean['userinput'], ENT_QUOTES, 'UTF-8');

    echo "<html><body><script>var text = '{$html['warning']}';</script>";
    echo "<script>alert(text);</script></body></html>";
    // continue on...

    is at risk? What do you need to do to become safe?

    (OWASP Guide author :)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>