File Upload Vulnerability : Security Check Bypass and Sanitization mitigation techniques

In this post we are gong to look at some of the security implementations and file control mechanism used to mitigate the File upload vulnerability and also how to bypass them. For the basics of File upload vulnerability please visit my previous post :

1. File Extension Blacklisting :

The file extension blacklists are widely used in file upload forms to blacklist all the file types that have dangerous extensions. But this type of validation checking is not much effective, and it could also be bypassed by using double extensions within the uploaded file names. In the file extension blacklisting the web application extracts file extensions by looking for the '.' character in the filename, and extracting the string after the dot character, and if the string is matched with the blacklisted file extension then it will refused to except the file. For example lets look at the below source code, which applies the file extension blacklist to filter the file with php extension.
<?php require_once("../header.php"); ?>

    $dir = '/var/www/upload/images/';
    $file = basename($_FILES['image']['name']);
    if (preg_match('/\.php$/',$file)) {
      DIE("NO PHP");
    if(move_uploaded_file($_FILES['image']['tmp_name'], $dir . $file))
      echo 'Upload done !';
      echo 'Your file can be found <a href="/upload/images/'.htmlentities($file).'">here</a>';
      echo 'Upload failed';

<form method="POST" action="example2.php" enctype="multipart/form-data">        
  Image: <input type="file" name="image"><br/>
  <input type="submit" name="send" value="Send file">

<?php require_once("../footer.php"); ?>
Code for filtering the php extension is :
if (preg_match('/\.php$/',$file)) {
  DIE("NO PHP");
Where the preg_match() function will try find the pattern ".php" at the end of uploaded files name and if it finds it then the script will DIE() with message "NO PHP". Now lets try to upload a simple web shell shell.php file here,

And as we expected the page will show the "NO PHP" message. This type of validation method is widely used in file upload forms to blacklist all the file types that have dangerous extensions. But this type of validation checking is not much effective, and it could also be bypassed by using double extensions within the uploaded file names.

But first It’s important to first understand how the target web server handles files with multiple extensions. In this example, it shall be assumed that the target server is Apache HTTP Server, the following is a quotation of the Apache HTTP Server documentation regarding files with multiple extensions.

Files can have more than one extension, and the order of the extensions is normally irrelevant. For example, if the file maps onto content type text/html and language French then the file will map onto exactly the same information. If more than one extension is given which maps onto the same type of meta-information, then the one to the right will be used, except for languages and content encodings. For example, if .gif maps to the MIME-type image/gif and .html maps to the MIME-type text/html, then the file welcome.gif.html will be associated with the MIME-type text/html.

Therefore, a file named filename.php.1, will be interpreted as a PHP file by Apache HTTP Server, and it will be executed. This of course, will only work if the last extension (in this case .1), is not specified in the list of MIME-types known to the web server.

Now lets rename our web shell from shell.php to shell.php.1 and then try to upload it.

This time the file is successfully uploaded onto the server. Now we can execute command. -a

There's also another method to bypass it, as we can see the php's extension filter code is case sensitive, means it only check the extension with lower case, so when we rename the file with "hello.PHP" then it will except the file.

Note : The above example is from Web4Pentester VM File Upload's example2.

2. MIME-type validation Check :

This technique is used to prevent file upload vulnerability by checking the uploaded file's MIME type, where the MIME-Type is used to identify the type of data. The MIME-type of uploaded file can get by the $_FILES global variable :
The above statement returns the MIME-type of uploaded file. The web application will check if uploaded file's MIME-type is matched to the accepted file type, if its match than it will accept the file otherwise the file is not uploaded. Now lets look at the code which implemented this type of security check :
  if (isset($_POST['Upload'])) { 
    $target_path = DVWA_WEB_PAGE_TO_ROOT."hackable/uploads/"; 
    $target_path = $target_path . basename($_FILES['uploaded']['name']); 
    $uploaded_name = $_FILES['uploaded']['name']; 
    $uploaded_type = $_FILES['uploaded']['type']; 
    $uploaded_size = $_FILES['uploaded']['size']; 

    if (($uploaded_type == "image/jpeg") && ($uploaded_size < 100000)){ 
      if(!move_uploaded_file($_FILES['uploaded']['tmp_name'], $target_path)) { 
        echo '<pre>'; 
        echo 'Your image was not uploaded.'; 
        echo '</pre>';                     
      } else {            
        echo '<pre>'; 
        echo $target_path . ' succesfully uploaded!'; 
        echo '</pre>';             
    } else { 
      echo '<pre>Your image was not uploaded.</pre>'; 
The above example is taken from DVWA FIle upload example with security level : medium.

As we can see that the variable $uploaded_type holds the MIME-type and it is compared with "image/jpeg". And when we upload php web shell then the MIME type is not match and it will show the error message "Your image was not uploaded".

The MIME-type of uploaded file is being sent to the server by using "Content-type" http request header, and we can see this by using a interceptor proxy like burp-suite, temper, ZAP proxy,etc. To setup burp proxy in your system and browser, take a look at this post :[burp proxy basics]. Now we click on the upload button then it intercept the http request, which looks like this :

And as we can see that the content type is here "Content-Type: application/x-php", application/x-php, which obviously does not match with "image/jpeg", and the web application will refused to upload it.

Now to bypass this we simply needs to change the Content-Type from 'application/x-php' to 'image/jpeg', and the web app will accept our web shell. So the modified HTTP request will look like below :

And our webshell will uploaded successfully.

Now we can execute command with this.

3. Checking image Headers :

This type of checking mechanism is used when only images are allowed to upload on the server. As name suggests it validates the uploaded image/file by checking the image header using a server side functions like getimagesize() in php.  The getimagesize() function returns the size of an image, and if the uploaded file is not an image file, means the file header is not the image header, then the function will return false.

Now lets see the implementation of this technique.

  if( isset( $_POST[ 'Upload' ] ) ) { 
    // Where are we going to be writing to? 
    $target_path  = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/"; 
    $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] ); 

    // File information 
    $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ]; 
    $uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1); 
    $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ]; 
    $uploaded_tmp  = $_FILES[ 'uploaded' ][ 'tmp_name' ]; 

    // Is it an image? 
    if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) && 
      ( $uploaded_size < 100000 ) && 
      getimagesize( $uploaded_tmp ) ) { 

      // Can we move the file to the upload folder? 
      if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) { 
        // No 
        echo '<pre>Your image was not uploaded.</pre>'; 
      } else { 
        // Yes! 
        echo "<pre>{$target_path} succesfully uploaded!</pre>"; 
    } else { 
      // Invalid file 
      echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>'; 


The above example is taken from DVWA FIle upload example with security level : high.

As we can see that the getimagesize() function is used in order to check the image headers, so if we try to upload webshell then it will shows error message.

Now to bypass the image header checking mechanism, we need to add the image header into our webshell. And we can use a hex editor to see the image headers. For example :

        PNG header : 89504E470D0A1A0A

So the above hexadecimal numbers are the PNG headers. Now we just need to add those headers into our php webshell. Again we can use any hex editor to edit the webshell file, i am using the  onlein one :

Above picture shows the hex representation of shell.php file. To edit the hex value right click on the hex value area and click on "insert bytes here..." option.

and then put 8 on the number of bytes box, then click on Apply button.

Now put all 8 bytes of PNG header hex value values, after that it will look like below :

Then click on "Export" button to download the file. But when we try to upload our edited webshell file, then the web app will again shows error message.

And the reason for this is that the web application will also check file for extension

But this can be easily bypassed with appending ".png" extension at end of the file, means renaming "shell.php" to "shell.php.png".

Now at this time we are able to successfully upload the file.

And we are above to execute command on the server through our webshell.

I wrote a simple python script to add png headers in php webshell files. Here is the code :

import binascii
import sys

if len(sys.argv) != 2:
  print "Argument missing!"
  print "Usage : %s <input_file>" % sys.argv[0]

pngHeader = "89504e470d0a1a0a"
# use below header for jpg/jpeg files
jpgHeader = "FFD8FFE000104A464946000101"

fileName = sys.argv[1]
f1 = open(fileName, "rb")
dt1 = binascii.hexlify(
dt2 = pngHeader + dt1
MainData = binascii.unhexlify(dt2)
newFile = fileName + ".png"
f2 = open(newFile, "wb")
print "File Successfully created : ", newFile
        Github : Download link

It simply uses binascii python module to convert raw binary into haxadecimal numbers, and append png headers in it then create a new file with ".png" extension.

Usage :
./ webshell.php
And it creates webshell.php.png file.

4. Protecting the upload folder with .htaccess

By placing .htaccess file on the upload directory, developers can prevent the execution of uploaded scripts on that directory. The .htaccess configuration file would look like this :
IndexIgnore *
# Disable script execution
AddHandler cgi-script .php .php2 .php3 .php4 .php5 .php6 .php7 .php8 .pl .py .js .jsp .asp .htm .html .shtml .sh .cgi
Options -ExecCGI -Indexes
But it is also not secure because, if the .htaccess file is on the upload directory, then an attacker could overwrite this file by uploading his own version of .htaccess file, and php's move_upload_file() function will overwrite any file if there already exists file with the same name.

There are two methods to trick apache server to execute files with safe extensions like jpg, png etc.

With SetHandler method :

First we need to upload the .htaccess file with below configuration.
<FilesMatch "_code.jpg">
  SetHandler application/x-httpd-php
The above code tells apache server to execute any file containing "_code.jpg" as valid php file by forcing through the SetHandler directive. Now write the above lines on ".htaccess" file and upload it on the web server.

And then create an image file with below php code :

Filename : image_code.jpg
<?php phpinfo(); ?>
And upload the file. Now open it on browser.

And as we can see the .jpg file is get executed as a valid php file.

With AddType Method :

With the AddType method we can map any random file extension to execute as a php file. To do this we just need to add below line to .htaccess file and upload it.
 AddType application/x-httpd-php .xyz
where ".xyz" is our random file format, which executes as php file. Also ceate a file with php code :

Filename :
<?php phpinfo(); ?>
Now lets test this :

And it works.

Conclusion :

In this post we look at some techniques to bypass FIle Upload Filters and security checks. In the Next post we look at some powerful Multi-functional webshells(weevely), and how to use them.

Visit the link for more tutorials about Web Security :