WordPress 3 core unauthenticated stored XSS

Overview

A security flaw in WordPress 3 allows injection of JavaScript into certain text fields. In particular, the problem affects comment boxes on WordPress posts and pages. These don’t require authentication by default.

The JavaScript injected into a comment is executed when the target user views it, either on a blog post, a page, or in the Comments section of the administrative Dashboard.

In the most obvious scenario the attacker leaves a comment containing the JavaScript and some links in order to put the comment in the moderation queue. The exploit is not then visible to normal users, search engines, etc.

When a blog administrator goes to the Dashboard/Comments section to review new comments, the JavaScript gets executed. The script can then perform operations with administrator privileges.

For instance, our PoC exploits first clean up traces of the injected script from the database, then perform other administrative tasks such as changing the current user’s password, adding a new administrator account, or using the plugin editor to write attacker-supplied PHP code on the server (this impact applies to any WordPress XSS if triggered by an administrator).

These operations happen in the background without the user seeing anything out of ordinary.

If the attacker writes new PHP code on the server via the plugin editor, another AJAX request can be used to execute it instantaneously, whereby the attacker gains operating system level access on the server.

The exploit will NOT be triggered directly at the Dashboard “root view” because only snippets (20 first words) of the latest comments are shown there with all HTML stripped.

If approved there, the exploit will be triggered by any user viewing the targeted blog posting or page, with their corresponding privileges.

Plugins that let unprivileged users to enter HTML text may offer other attack vectors.

Details

WordPress allows a few HTML tags in comments, such as the anchor <A>, bold <B>, and code <CODE> tags. Certain white-listed attributes are allowed in each tag. Obviously, the “href” attribute is important for anchor tags, but e.g. the “onmouseover” attribute would be undesirable.

The problem occurs in a text formatting function called wptexturize() which is normally executed for each comment and other blocks of text. The function replaces certain simple characters with fancier HTML entities. For instance, straight quote symbols are replaced with opening and closing curly quotes, unicode 8220 and 8221.

In order to avoid interfering with HTML formatting, wptexturize() first splits the text in segments. The splitting is expected to pick HTML tags (which aren’t texturized) apart from running text (which is texturized).

In addition to HTML tags, the code is supposed to recognize square-bracketed shortcodes such as [CODE] and avoid texturizing them.

The splitting is implemented with a regular expression in wp-includes/formatting.php:

    $textarr = preg_split('/(<.*>|\[.*\])/Us', $text, -1, PREG_SPLIT_DELIM_CAPTURE);

A text containing carefully mixed square and angle brackets confuses the splitting process and results in HTML code getting partially texturized.

An attacker can exploit the bug to supply any attributes in the allowed HTML tags. A style attribute can be used to create a transparent tag covering the whole window, forcing the execution of its onmouseover handler.

In practical applications the script would probably first remove the transparent tag to avoid interfering with UI events and re-triggering the handler. It could then insert a new <SCRIPT> tag to load a more complex JavaScript file to execute from another web server. This script can use e.g. jQuery to chain AJAX operations for posting HTML forms and retrieving the required nonces.

Affected versions

We tested a few WordPress versions from 3.0 to the latest 3.9.2. All tested versions were vulnerable. The problem seems to have gone uncorrected for almost four years. As of September 2014, about 90% of all WordPress sites were running a vulnerable version. On November 20, the percentage is down to about 86%.

Version 4.0 uses a different kind of regular expression and is NOT vulnerable to this problem.

Workarounds

Texturizing can be easily disabled by adding a return statement in the beginning of the function in wp-includes/formatting.php:

  function wptexturize($text) {
        return $text;                  // ADD THIS LINE
        global $wp_cockneyreplace;

This changes how some punctuation marks look like but the difference is quite minor.

The preferred solution should still be applying the official patch.

Vendor response

WordPress was notified on September 26 and has released patches correcting the problem. At the moment of writing they have been deployed as automatic updates to WordPress users. The advisory is available here.

Credits

The vulnerability was discovered and researched by Jouko Pynnonen, Klikki Oy, Finland.

Update December 1, 2014: a proof of concept exploit published.

Update January 31, 2015: another 0-day with a similar impact to be released soon. Vendor notified on November 7.

Update April 25, 2015: exploit attempts in the wild

Update July 27, 2015: The “another 0-day” referred above is now public, read more. Meanwhile there was one more comment XSS found by us, read more.