Tag: file upload
2011
10.03

Summary

Scripts using PHP 5.3 that accept multiple file uploads in a single request are potentially vulnerable to a directory traversal attack. Information about the mechanism for attack (corrupting array indices in $_FILES) has been publicly available since at least March 2011 June 2009. [1] [2] [3] [4] I submitted Sec Bug #55500 to point out the potential for directory traversal on August 24th, 2011.

[Note: I’ve been informed that a similar attack using the same vector was mentioned in the PHP Bug Tracker in September 2009. [5]]

[Update: As of January 1st 2012, a fix for this issue has been committed for PHP 5.4 and trunk in SVN r321664]

How does it work

The NYU Poly ISIS Lab blog has a wonderful, detailed writeup discussing how it’s possible to cause PHP to mangle indices in the $_FILES array. In short, multi-part POST requests containing variables with extra opening square brackets will cause PHP to build a malformed $_FILES array.

All of the previous writeups on this topic have focused on attacks where the target script is using copy instead of move_uploaded_file to handle uploads. While that assumption does allow for some interesting exploitation, it’s a less realistic scenario. I decided to focus exclusively on the implications of an attack if a target uses the move_uploaded_file function.

Consider the following script, based on code from the PHP documentation: [6] [7]

 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

print_r($_FILES);

if (!empty($_FILES['pictures']))
{
    // Modified slightly from http://php.net/manual/en/function.move-uploaded-file.php
    $uploads_dir = '.';
    foreach ($_FILES["pictures"]["error"] as $key => $error) {
        if ($error == UPLOAD_ERR_OK) {
            $tmp_name = $_FILES["pictures"]["tmp_name"][$key];
            $name = $_FILES["pictures"]["name"][$key];
            echo "move_uploaded_file($tmp_name, \"$uploads_dir/$name\");";
        }
    }
}
?>
<form action="" method="POST" enctype="multipart/form-data" >
<input type="hidden" name="MAX_FILE_SIZE" value="10000000">
<input type="file" name="pictures[[type]">
<input type="file" name="pictures[[name]">
<input type="file" name="pictures[name][">
<input type="submit" value="submit">
</form>

If you play around with sending requests to this script, you’ll see that it wants to move the contents of the file uploaded as pictures[[type] into a location specified by pictures[name][‘s Content-Type. Since an uploaded file’s Content-Type is not sanitized by PHP, any attacker can send a request with any value, allowing for directory traversal.

But you shouldn’t accept an unsanitized filename from users! The bug is in the script, not the language!

Yes. And no.

Yes, leaving file uploads completely unsanitized is a bad idea. The code sample I posted above is vulnerable to a number of different attacks in its current state, the most glaring of which is that an attacker can upload arbitrary PHP files.

However, under normal circumstances PHP sanitizes the name parameter of the $_FILES array to prevent directory traversal attacks. That’s a language feature which developers may be relying on for security and which PHP’s maintainers have released security patches for in the past. Just a few months ago, Krzysztof Kotowicz discovered an off-by-one error in PHP’s file upload sanitization routine which made it possible for an attacker to write to the root of a drive. That bug, #54939, was fixed in PHP 5.3.7.

Workarounds

If you have a vulnerable script, you can mitigate the attack by calling basename on the user-provided filename. It should also be possible to detect malicious requests because of the mangled index names.

Edit: Adam pointed out an older bug report referencing the array corruption issue and potential for security concerns. I’ve updated the post to reflect the new information.

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.