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.
Recon
Once the VPN is up, we can get going with a port scan.
Nothing really special here, just HTTP and SSH ports. This looks like an Ubuntu box from the SSH version. Let's fire up a browser and see what's on that web server.
Main qualification is "Can use burp," that's amusing, but I won't knock a newer team. Bad site security, on the other hand . . . well let's take a look. The Portal page is the only thing that seems to have any functionality here. Let's inspect that.
Foothold
So it looks like the database isn't actually functioning, but we do have a ticketing system of some kind, likely to log bounties. I do want to see what's going through to the other end, but first let's try a few things. I did see that the site is running PHP, so let's try PHP injection?
No PHP injection, okay. However, looking at the console, I saw I had a DOM error coming from the JavaScript. Interesting. I looked into that and it looked like it was firing off XML to a "dirb-proofed" tracker page. The XML made my brain immediately jump to XXE (XML External Entity) exploits. Question was, how could I intercept that and change it to fit my needs better?
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 back to plain text, 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 goofed hard and forgot to dirbust.
Now, before I get into actually getting user on this box, I want to say that XXE is actually relatively easy to remediate, and mostly comes up in PHP code, but can really crop up in any XML API. This mostly comes down to not allowing any sort of user defined structures in the XML, and only using internal validators on the server. For more information, I'd recommend reading the OWASP prevention page here.
Getting User
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, probably because they hadn't actually gotten around to deploying a database yet.
Extracting db.php, you find that oh great they hard-coded credentials. Never do that if you can avoid it, you always want to use environment variables or keystores or something that isn't in your source code, because you never know what could be extracted.
There's a good chance that the user reused the password for their user account on the box, so let's try that same password with the SSH username "development."
Lo and behold, that worked, and I had a shell. Let's go for root now.
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. Can you guess 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.
Of course, literally nothing in the script seems to require root privileges, so I'm not sure why the writers did that in this hypothetical scenario. Definitely don't have calls to code evaluation functions with user supplied data. And definitely don't have them be run as root.
The gist of this exploit, then, 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.
That import part is a one line way of importing the os module and executing a shell, and it works quite handily here. Fire off the ticket into the script and...
Boom. There's our lovely little root shell.
Shenanigans
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.
Conclusion
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 hard-coding credentials, and what happens when you use "eval" with user input.
PS
I recently (July 2022) had to reupload this writeup to dev.to, and it had been a while since I first wrote it, so I touched up a few things and added some remediation steps. Hope you find this useful!
Top comments (0)