TwoMillion Writeup - HackTheBox
Last updated May 10, 2026 • 5 min read
Discovery
Port Scan
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 80/tcp open http nginx |_http-title: Did not follow redirect to http://2million.htb/
Add 2million.htb to /etc/hosts. Port 80 redirects to the hostname — this is a nostalgic recreation of the original HackTheBox platform.
Directory Enumeration
/home (Status: 200) /register (Status: 200) /login (Status: 200) /api (Status: 401) /invite (Status: 200)
/register redirects to /invite — registration requires an invite code. /api returns 401 unauthenticated.
Initial Access
Invite Code Generation
Browsing to /invite loads a form. The page source pulls in /js/inviteapi.min.js. The obfuscated JavaScript, when de-obfuscated and evaluated, reveals:
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) }
})
}
Call the endpoint:
{
"data": {
"data": "Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb \/ncv\/i1\/vaivgr\/trarengr",
"enctype": "ROT13"
}
}
Decode the ROT13 string: In order to generate the invite code, make a POST request to /api/v1/invite/generate.
{"data": {"code": "TjBVU1YtM1ZPVTAtNTcyVU8tRTI0UjA=", "format": "encoded"}}
Base64-decode the code: N0USV-3VOU0-572UO-E24R0. Use it to register an account at /register, then log in.
API Enumeration
With a valid session cookie, enumerate the API:
{
"v1": {
"user": {
"GET": {
"/api/v1/user/vpn/generate": "Generate a new VPN configuration"
},
"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"
}
}
}
}
Three admin routes stand out: PUT /api/v1/admin/settings/update is interesting — it's a settings update endpoint that shouldn't be reachable by regular users.
Broken Access Control — Self-Promotion to Admin
Test the PUT endpoint with the session cookie:
{"status":"danger","message":"Missing parameter: email"}
The endpoint accepts the request — no admin check enforced. Probe for required parameters by adding them incrementally:
{"status":"danger","message":"Missing parameter: is_admin"}
{"id":20,"username":"$USER","is_admin":1}
The API promotes the account to admin without any server-side authorisation check.
Command Injection in VPN Generator
With admin privileges, POST /api/v1/admin/vpn/generate is now accessible. It accepts a username parameter and generates an OpenVPN config. Test for command injection:
Start a listener first:
Shell lands as www-data. Stabilise it:
Credential Discovery
The web root contains a .env file:
DB_HOST=127.0.0.1 DB_DATABASE=htb_prod DB_USERNAME=admin DB_PASSWORD=$ADMIN_PASSWORD
SSH as admin
The database password works for the system account directly.
Privilege Escalation
CVE-2023-0386 — OverlayFS FUSE Privilege Escalation
A mail tip in /var/mail/admin points toward the vulnerability:
Subject: Urgent: Patch System OS
Hey admin, [...] That one in OverlayFS / FUSE looks nasty. We can't get popped by that.
The kernel version confirms exposure:
5.15.70-051570-generic
CVE-2023-0386 is an OverlayFS vulnerability in Linux kernels before 6.2 that allows an unprivileged user to copy a SUID binary into an OverlayFS mount via FUSE, bypassing the normal restriction that strips SUID bits on copy. The result is a SUID-root binary writable and executable by the current user.
Clone and compile the PoC from puckiestyle/CVE-2023-0386 on your attacker machine. The repo requires two binaries: fuse and exp, plus a helper gc. Compile them:
Serve the binaries and transfer them to the target:
Create the required directory structure and run fuse in the background to set up the FUSE filesystem mount:
[+] len of gc: 0x3f38 [+] readdir [+] getattr_callback /file [+] open_callback /file [+] read buf callback offset 0 size 16384 path /file [+] ioctl callback path /file cmd 0x80086601
Trigger the exploit:
uid:1000 gid:1000 [+] mount success total 8 drwxrwxr-x 1 root root 4096 Jan 9 20:37 . drwxrwxr-x 6 root root 4096 Jan 9 20:37 .. -rwsrwxrwx 1 nobody nogroup 16184 Jan 1 1970 file [+] exploit success!
5.15.70-051570-generic