You'd think that a group of bug bounty hunters would know to make their site at least somewhat secure. This is a good look at what happens when you assume XML input won't get messed with, and why you should never use Python's eval() function if you can avoid it.
VPN's up, box started, let's start with a port scan.
Nothing really special here, just an HTTP and SSH port. This is an Ubuntu box from the service versions. Let's fire up a browser and see what's on port 80.
The Contact and About pages don't do much of anything, which is nice for a lack of red herrings. Now the Portal page looks interesting. Follow the link and you get to this form, which has a bug bounty submission form, including title, CWE, and other info. If you fire off some junk data, you get a response directly in the page.
Well, since the page is running PHP, I figured a code injection might be worth a shot. Let's try <?php echo system("id")>.
First things first was capturing the request to the tracker. The POST data had one parameter, data, which had what I originally thought was base64-encoded data. I dropped that into Cyberchef and confirmed that yes, this was base64 encoded XML getting fired off to the server. Lovely.
The next step was crafting payloads and launch methods. First was building the launch, I whipped up a Python script to replicate the XML I wanted to fire off, and then encode that as base64. Note the doctype and entity code bit, that's important for the attack as it's what is actually doing most of the work. Then I left an fstring for my XXE payload and was off to the races.
In short, the way XXE works is you take advantage of a site using externally loaded XML to load things that are, usually, not XML the site expects. For instance, the classic /etc/passwd file. That was achieved with a payload of "file:///etc/passwd", and, drum roll please . . .
Tada! We have a list of every user on the box. Looks like we just have development and good old root, so no user hopping this time. I tried a number of different files, ranging from /etc/network/interfaces to /home/development/.ssh/id_rsa in hopes of a key auth. Only the /etc stuff really worked, I couldn't get any of the PHP code to load, though. However, I remembered a trick I learned in a CTF, which involved the PHP wrapper of php://filter, and allowed you to convert data to base64. So, I used that, extracted the tracker source as base64, converted it, and I had the page source on my local machine. I did this for the other pages as well, but now I was a little stuck. Where do I go from here? There aren't any passwords lying around in easily accessible places.
Then I realized I forgot something.
Directory enumeration pointed me to the two things I was missing to crack this user flag: a README.txt in the /resources URI, and a file called db.php. The README.txt was a good read, since it gave some more context to the development situation. The dev hadn't quite finished hooking up the database, and yet the website was live . . . huh.
I extracted the db.php and oh look there's the user creds.
SSH in with "development" as the username, there's the user flag. Cat, submit, celebrate a little.
Let's move on to privesc.
First instinct for me upon getting user is sudo -l. Not met with a password this time, and it looks like we do actually have an interesting allowance. A ticket validator written in Python. We have a contract.txt file in the user directory that supports the fact that this script is evidently critical. I'm curious as to why you'd need sudo to run that.
Extracting the file, we get this. I'm spotting one thing that really shouldn't be there. Take a wild guess as to what it is.
Yep. It's the "eval" call. In Python, that is insecure as all hell, especially with root permissions. In this case, it looks like it's being used to validate the ticket numbers. We can use this to run system commands as root, including firing up shells.
The gist of this exploit is we need to get to the point where the ticket evaluator makes that "eval" call, and then shove in a shell and we have root.
I discovered the best way to do this in one line was to use __import__('os').system('/bin/bash') instead of the usual import os; os.system('/bin/bash'). The rest of the conditions met, I fired in my modified ticket and . . .
Boom. There's our lovely little root shell. Cat, submit, you know what to do.
Before I realized I forgot the directory enum, I tried to take the XXE further than necessary by attempting a remote code execution inside of it. Of course, the expect:// module isn't typically loaded in PHP, so that worked about as well as trying to play guitar but with all the strings cut.
I looked around at some alternatives to actual RCE and found an interesting route involving scanning a subnet through XXE. Huh. Guess I could try that.
I used the XXE method to grab the /etc/network/interfaces file, then used the subnet mask to grab the HTTP pages of every box on the network. Gave me some good sneak peeks into what other challenges were! I could have gone further and ran directory enumeration, but that's a project for another day.
This was my first box on the HackTheBox platform, and I really enjoyed it! I've run boxes on CyberSecLabs before, so I was right at home with this one. A great look at the perils of relying on Base64 to hide your unchecked XML-based traffic, and what happens when you use "eval" as a ticket validator.