How to Prevent File Upload Vulnerabilities
The WordPress Security Learning Center
How to Prevent File Upload Vulnerabilities

2.4: How to Prevent File Upload Vulnerabilities

Advanced
Updated September 30, 2024

File Upload Vulnerabilities are the third most common vulnerability type that we found in our vulnerability analysis of 1599 WordPress vulnerabilities over 14 months.

File-Upload

The Impact of File Upload Vulnerabilities

In the video demonstration below we show how a file upload vulnerability is detected by an attacker on a vulnerable website. The attacker then uses Metasploit to get a remote shell on the website. We show the capabilities that a remote shell provides an attacker. The video clearly demonstrates that file upload vulnerabilities are extremely serious and very easy to exploit.

 

Types of File Upload Vulnerability

There are two basic kinds of file upload vulnerabilities. We are going to give these descriptive names in this article that you may not have heard elsewhere, but we feel these describe the difference between the basic types of upload vulnerability.

A local file upload vulnerability is a vulnerability where an application allows a user to upload a malicious file directly which is then executed.

A remote file upload vulnerability is a vulnerability where an application uses user input to fetch a remote file from a site on the Internet and store it locally. This file is then executed by an attacker.

Lets look at each of these vulnerabilities in some detail, how they are created and how to avoid them.

Local File Upload Vulnerability

To examine this vulnerability, lets look at the ‘wpshop’ plugin file upload vulnerability reported in early 2015. Here is the code that created the vulnerability:

$file = $_FILES['wpshop_file'];
$tmp_name = $file['tmp_name'];
$name = $file["name"];
@move_uploaded_file($tmp_name, WPSHOP_UPLOAD_DIR.$name);

You can find this code at line 620 of includes/ajax.php in version 1.3.9.5 of the plugin.

The code above makes two critical mistakes which create a file upload vulnerability.

Mistake 1: There is no authentication or authorization check to make sure that the user has signed in (authentication) and has access to perform a file upload (authorization). This allows an attacker to upload a file to the website without needing to sign-in or to have the correct permissions.

As a developer, you can avoid this mistake by verifying the user has permissions to upload files before processing the file upload:

if (!current_user_can('upload_files')) // Verify the current user can upload files
	wp_die(__('You do not have permission to upload files.'));

// Process file upload

Mistake 2: There is no sanitization on the file name or contents. This allows an attacker to upload a file with a .php extension which can then be accessed by the attacker from the web and executed.

Developers can avoid this mistake by sanitizing the file name so that it does not contain an extension that can execute code via the web server. WordPress has some built-in functions to check and sanitize files before uploading.

wp_check_filetype() will verify the file’s extension is allowed to be uploaded, and, by default, WordPress’s list of allowable file uploads prevents any executable code from being uploaded.

$fileInfo = wp_check_filetype(basename($_FILES['wpshop_file']['name']));
if (!empty($fileInfo['ext'])) {
	// This file is valid
} else {
	// Invalid file
}

You can also further limit what is allowed by specifying the mime types allowed. This list allows only images.

// We are only allowing images
$allowedMimes = array(
	'jpg|jpeg|jpe' => 'image/jpeg',
	'gif'          => 'image/gif',
	'png'          => 'image/png',
);

$fileInfo = wp_check_filetype(basename($_FILES['wpshop_file']['name']), $allowedMimes);

Now that we have verified the file name is safe, we’ll handle the file upload itself. WordPress has a handy built-in function to do this: wp_handle_upload().

$fileInfo = wp_check_filetype(basename($_FILES['wpshop_file']['name']));

if (!empty($fileInfo['type'])) {
	$uploadInfo = wp_handle_upload($_FILES['wpshop_file'], array(
		'test_form' => false,
		'mimes'     => $allowedMimes,
	));
}

wp_handle_upload() takes a reference to a single element of the $_FILES super-global and returns an array containing the URL, full path, and mime type of the upload.

Check upload content for extra security

When receiving an upload, you can avoid attackers uploading executable PHP or other code by examining your uploads for content. For example, if you are accepting image uploads, call the PHP getimagesize() function on the uploaded file to determine if it is a valid image.

getimagesize() attempts to read the header information of the image and will fail on an invalid image. This is another method to verify the content you’re expecting from the user.

if (!@getimagesize($_FILES['wpshop_file']['tmp_name']))
	wp_die(__('An invalid image was supplied.'));

Remote File Upload Vulnerability

A remote file upload vulnerability is when an application does not accept uploads directly from site visitors. Instead, a visitor can provide a URL on the web that the application will use to fetch a file. That file will be saved to disk in a publicly accessible directory. An attacker may then access that file, execute it and gain access to the site.

The TimThumb vulnerability which affected a very large number of plugins and themes was a remote file upload vulnerability. In the case of TimThumb, the image library provided developers with a way to specify an image URL in the query string so that TimThumb.php would then fetch that image from the web.

The image URL could be manipulated so that an attacker could specify a PHP file which was hosted on the attackers own website. TimThumb would then fetch that PHP file and store it on the victim website in a directory accessible from the web. The attacker would then simply access that PHP file in their browser and be able to execute it.

How to avoid remote file upload vulnerabilities

Avoiding this kind of vulnerability is similar to avoiding a local file upload vulnerability:

  • Only allow specific file extensions.
  • Only allow authorized and authenticated users to use the feature.
  • Check any file fetched from the Web for content. Make sure it is actually an image or whatever file type you expect.
  • Serve fetched files from your application rather than directly via the web server.
  • Store files in a non-public accessibly directory if you can.
  • Write to the file when you store it to include a header that makes it non-executable.

Conclusion

As you can see from the video demonstration and the content above, file upload vulnerabilities are serious. They are also easily avoided once a developer can recognize them and there are several effective techniques available to prevent this kind of vulnerability affecting your WordPress application.

 

Did you enjoy this post? Share it!

The WordPress Security Learning Center

From WordPress security fundamentals to expert developer resources, this learning center is meant for every skill level. Get serious about WordPress Security, start right here.