Web App – File Upload Vulnerabilities

Today we will discuss file upload vulnerabilities; a topic that is widely underestimated by developers. First, we will imagine a website in which it is possible to upload images with the format .jpg, .png, .gif and so on. If an application does not have proper form validation for file uploads, an attacker is able to gain control over the system. This is especially true for file extensions like .php and .asp, since these are automatically interpreted by webservers. I’ll elaborate on how this works, and on which defense strategy will be effective depending on each specific case.

File upload vulnerabilities are a devastating category of web application vulnerabilities. Without secure coding and configuration an attacker can quickly compromise an affected system.

The quotation from Matt Koch in 2015 highlights the importance of file upload vulnerabilities in these days. File upload vulnerabilities have been identified in a study of 1600 WordPress pages as third most common vulnerability. As of January 2016th.

In the simplest case, there are no restrictions on the type of the file, therefore an attacker can easily upload malicious code. This looks naive, but is still common.


<!DOCTYPE html>
        <form action="upload.php" method="post" enctype="multipart/form-data">
            Select image to upload:
            <input type="file" name="uploadedfile" id="uploadedfile">
            <input type="submit" value="Upload Image" name="submit">

Above is a simple HTML code for an HTTP file upload in which the encryption type specifies the method of encoding. If this is set multipart, no character is encoded, but the user agent is adding some information to the request body or payload.

Getting a POST request with the encoding type multipart / form-data PHP first generates a temporary file with a random name in a temporary directory (example: /var/tmp/php6yXOVs).

Next up a gloable $_FILES array for the file is created that contains information about the uploaded files:

  • <name> specifies the original name of the file given by the user,
  • <type> describes the MIME type,
  • <size> is self explanatory and
  • <tmp_name> is the temporary name given by PHP.

Now we will have a quick look into the PHP code, written to utilize the upload form on the server.

    $target_file = getcwd()."/uploads/".basename($_FILES["fileToUpload"]["name"]);
    if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {   
        echo "The file has been uploaded.";
    else  {
        echo "Sorry, there was an error uploading your file.";

The function move_uploaded_file() moves the temporary file to the location, which is passed as second argument into the function call. What might an attacker do here? Because there is no mechanism for validation, an attacker can easily upload malicious code to the server, determine the directory of upload and execute the mailicious code. Examples of the single chapters can be downloaded in the corresponding git repository.


What code can an attacker upload and what can he exploit? To provide a quick overview, we will discuss exploit opportunities in a nutshell. An exploit is a way to make use of a programmatic vulnerability. Comparable with a burglar with a crowbar, an attacker obtains access to without permission. They exploit using web shells / backdoor shells.

A backdoor shell is a piece of malicious code that can be uploaded onto a server allowing access to the server’s file system. Once uploaded, the attacker can execute any actions on the infected server. There are a variety of open source webshells available online. One of the best known is the WSO. You can get a quick look at it in the following pastebin. Many webservers are running with root privileges. In these cases, the attacker then has captured the whole domain.


What can we do to protect ourselves against these attacks? One option is to validate the MIME type of the file:

   $valid_mime_types = array(
    if (in_array($_FILES["fileToUpload"]["type"], $valid_mime_types)) {
        moveFile($_FILES, $target_file);
        echo "MIME Type not supported";

We create an array of allowable MIME types and compare these with the type that was uploaded. If the MIME types matches, a file is stored.

Why is this not a good tactic? The answer lies in the PHP documentation of global PHP file arrays: “This value is completely under the control of the client and not checked on the PHP side”. An attacker could manipulate the MIME type. There are many extensions like Tamper Data that can create fake MIME types. An implementation in PHP is available in my following Gist.


Which is the right solution? Black lists of suspicious file extensions?


    $non_valid_file_extensions = array(".php", ".asp");
    $file_extension = strrchr($_FILES["fileToUpload"]["name"], ".");
    if (!in_array($file_extension, $non_valid_file_extensions)) {
        moveFile($_FILES, $target_file);
        echo "MIME Type not supported";

Many webservers allow doubled File Extensions. Since the code only recognizes the last extension, an attacker can upload a file with a double extension (PHPINFO.php.123). The upload should work, but the execution does not work necessarily. However, there is a further bypass: .htaccess

.htaccess is a configuration file where directory-based rules can be set up.

An attacker first uploads malicious PHP code with the file extension .mp3 (phpinfo.mp3). He then uploads a .htaccess with the following content:

AddType application/x-httpd-php .mp3

This line ensures that .mp3-files that are stored in the same directory as the .htaccess-file, are interpreted as PHP code.


Many developers use the function getimagesize to validate an image:


    $target_file = getcwd()."/uploads/".basename($_FILES["fileToUpload"]["name"]);
    $imageFileType = pathinfo($target_file,PATHINFO_EXTENSION);
    $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
    if($check !== false) {
        moveFile($_FILES, $target_file);
    } else {
        echo "MIME Type not supported";

By calling this function the size of the image will be returned. If the image is not valid, the function returns the boolean false. Therefore, developers check whether a Boolean is set or not.

When the attacker embeds simple PHP code in an image this function will return false. Even here, we can use a simple bypass: The image can be opened in an image editor such as GIMP or Photoshop, and the malicious code can be covert within meta information of the image. Thus the image still has valid header informations and the function will not return the boolean anymore.


As we have seen, there are many ways an attacker can bypass the security mechanisms of file uploads. A combination of several bypasses that were shown, are also conceivable.

If the attacker not only uploads a phpinfo()-function, but a whole webshell, he has shell access even to the whole system.

At this point I would like to to offer you some comments on how to protect yourself against these attacks. The best tactic is to set up basic security mechanisms:

1. to prevent the upload of malicious code:
– Create a whitelist of valid MIME types. Never blacklist things.
– Check the file extension.
– Check for a real image.

2. to prevent the execution of malicious code:
– Generate a random filename, so the attacker can not find the URL to execute the script.
– Set up a .htaccess in the parent directory which only grants access to files with valid extension.
– Use the principle of least privilege for your webserver-user! Never run a webserver as root! This is the most important advice anyway!
– You could even think about uploading images to a dedicated storage where no compiler is installed.


If you do not write the web app yourself, but you rely on a content management system such as WordPress, Drupal or else, there are a lot of available tools online that indicate you file upload vulnerabilities. For WordPress you can use the Command Line Interface wpscan.

Future Research

Many developers do not have a sense for security topics. So how can vulnerabilites of a system in terms of file uploads be avoided without a developer dealing with it? Can these problems be solved by a system administrator or even a service?

Leave a Reply