> For the complete documentation index, see [llms.txt](https://ret2basic.gitbook.io/ctfnote/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ret2basic.gitbook.io/ctfnote/web/file-upload/code-review-bwapp-unrestricted-file-upload.md).

# Code Review: bWAPP Unrestricted File Upload

## Source Code

As an example, we are going to do a code review on bWAPP "Unrestricted File Upload" challenges:

{% embed url="<https://github.com/ret2basic/bWAPP-Source-Code/blob/master/bWAPP/unrestricted_file_upload.php>" %}
bWAPP Source Code
{% endembed %}

## Security Level 0

In this level, there is no filter at all:

```php
        case "0" : 
            
            move_uploaded_file($_FILES["file"]["tmp_name"], "images/" . $_FILES["file"]["name"]);
            
            break;
```

We can simply upload a PHP webshell with names like `webshell.php` and this webshell can be found at `images/`.&#x20;

## Security Level 1

In this level, the filter used is called `file_upload_check_1`:

```php
        case "1" :
            
            $file_error = file_upload_check_1($_FILES["file"]);
            
            if(!$file_error)
            {
                
                move_uploaded_file($_FILES["file"]["tmp_name"], "images/" . $_FILES["file"]["name"]);
    
            }            
            
            break;
```

This function is defined in `functions_external.php`:

```php
function file_upload_check_1($file, $file_extensions  = array("asp", "aspx", "dll", "exe", "jsp", "php"), $directory = "images")
{
    
    $file_error = "";
    
    // Checks if the input field is empty
    if($file["name"] == "")
    {
        
        $file_error = "Please select a file...";
        
        return $file_error;
        
    }
    
    // Checks if there is an error with the file
    switch($file["error"])
    
    // URL: http://php.net/manual/en/features.file-upload.errors.php
    
    {
        
        case 1 : $file_error = "Sorry, the file is too large. Please try again...";
                 break;
             
        case 2 : $file_error = "Sorry, the file is too large. Please try again...";
                 break;
             
        case 3 : $file_error = "Sorry, the file was only partially uploaded. Please try again...";
                 break;
             
        case 6 : $file_error = "Sorry, a temporary folder is missing. Please try again...";
                 break;
             
        case 7 : $file_error = "Sorry, the file could not be written. Please try again...";
                 break;
             
        case 8 : $file_error = "Sorry, a PHP extension stopped the file upload. Please try again...";
                 break;
             
    }
    
    if($file_error)
    {
        
        return $file_error;
        
    }
    
    // Breaks the file in pieces (.) All pieces are put in an array
    $file_array = explode(".", $file["name"]);
    
    // Puts the last part of the array (= the file extension) in a new variabele
    // Converts the characters to lower case
    $file_extension = strtolower($file_array[count($file_array) - 1]);
    
    // Searches if the file extension exists in the 'allowed' file extensions array   
    if(in_array($file_extension, $file_extensions))
    {
        
       $file_error = "Sorry, the file extension is not allowed. The following extensions are blocked: <b>" . join(", ", $file_extensions) . "</b>";
       
       return $file_error;
       
    }
    
    // Checks if the file already exists in the directory
    if(is_file("$directory/" . $file["name"]))
    {
        
        $file_error = "Sorry, the file already exists. Please rename the file...";      
        
    }
    
    return $file_error;
    
}
```

This implementation uses **blacklist** to filter out unwanted file extensions. Specifically, the following file extensions are blocked:

* asp
* aspx
* dll
* exe
* jsp
* php

Of course this implementation is not sufficient at all. For example, we can try things like php3/php4/php5 as file extension and bypass the check.

## Security Level 2

In this level, the filter used is called `file_upload_check_2`:

```php
        case "2" :            
                       
            $file_error = file_upload_check_2($_FILES["file"], array("jpg","png"));
            
            if(!$file_error)
            {
                
                move_uploaded_file($_FILES["file"]["tmp_name"], "images/" . $_FILES["file"]["name"]);
    
            }            
            
            break;
```

This function is defined in `functions_external.php`:

```php
function file_upload_check_2($file, $file_extensions  = array("jpeg", "jpg", "png", "gif"), $directory = "images")
{
    
    $file_error = "";
    
    // Checks if the input field is empty
    if($file["name"] == "")
    {
        
        $file_error = "Please select a file...";
        
        return $file_error;
        
    }
    
    // Checks if there is an error with the file
    switch($file["error"])
    
    // URL: http://php.net/manual/en/features.file-upload.errors.php
    
    {
        
        case 1 : $file_error = "Sorry, the file is too large. Please try again...";
                 break;
             
        case 2 : $file_error = "Sorry, the file is too large. Please try again...";
                 break;
             
        case 3 : $file_error = "Sorry, the file was only partially uploaded. Please try again...";
                 break;
             
        case 6 : $file_error = "Sorry, a temporary folder is missing. Please try again...";
                 break;
             
        case 7 : $file_error = "Sorry, the file could not be written. Please try again...";
                 break;
             
        case 8 : $file_error = "Sorry, a PHP extension stopped the file upload. Please try again...";
                 break;
             
    }
    
    if($file_error)
    {
        
        return $file_error;
        
    }
    
    // Breaks the file in pieces (.) All pieces are put in an array
    $file_array = explode(".", $file["name"]);
    
    // Puts the last part of the array (= the file extension) in a new variabele
    // Converts the characters to lower case
    $file_extension = strtolower($file_array[count($file_array) - 1]);
    
    // Searches if the file extension exists in the 'allowed' file extensions array   
    if(!in_array($file_extension, $file_extensions))
    {
        
       $file_error = "Sorry, the file extension is not allowed. Only the following extensions are allowed: <b>" . join(", ", $file_extensions) . "</b>";
       
       return $file_error;
       
    }
    
    // Checks if the file already exists in the directory
    if(is_file("$directory/" . $file["name"]))
    {
        
        $file_error = "Sorry, the file already exists. Please rename the file...";      
        
    }
    
    return $file_error;
    
}
```

This implementation uses **whitelist** to only allow wanted file extensions. Specifically, the following file extensions are accepted:

* jpeg
* jpg
* png
* gif

This implementation looks sufficient at first, but if you dig deeper into the function calls, you will find this line of code:

```php
if(!in_array($file_extension, $file_extensions))
{
    ...
}
```

Take a look at the PHP manual:

{% embed url="<https://www.php.net/manual/en/function.in-array.php>" %}
in\_array - PHP Manual
{% endembed %}

The nuance is that the `in_array()` function takes 3 arguments but this code only used 2. The third argument `bool $strict` is set to `false` by default, which indicates **loose comparison**. PHP loose comparison is a weird "feature" and it is the source of many dumb vulnerabilities. For example:

```php
$values = array("apple", "orange", "pear", "grape");
var_dump(in_array(0, $values));
```

You may think the output will be `bool(false)`, but no, the output is `bool(true)`! This is because in loose comparison, we have `0 == "apple"`. In fact, every string started with letter is evalated to 0 in loose comparison. I am speechless.
