Bonnie has confirmed the location of the Acnologia spacecraft operated by the Golden Fang mercenary. Before taking over the spaceship, we need to disable its security measures. Ulysses discovered an accessible firmware management portal for the spacecraft. Can you help him get in?
Initial thoughts
For this challenge, we’re given an endpoint to the “Acnologia Firmware Portal”, which is just a login screen as well as a zip file containing the source code. The code looks to be written using the Flask framework.
In routes.py we can see all the endpoints available to us. Many of the endpoints are publicly available, but some require you to be logged in, be an administrator, or some combination of the two.
I created an account, and after logging in was given a list of firmware and the ability to report bugs.
Looking at routes.py I could see that after posting a firmware report, a function visit_report() is called. This function launches a selenium driver in bot.py that visits the report review page using a bot with administrative credentials. This immediately indicates an XXS injection.
If we look at the Jinja2 template for the review page, we confirm our XXS theory. The {{ report.issue | safe }} directive tells Jinja2 not to sanitize the value of report.issue. We can control the value of report.issue when we submit a report, allowing us to craft an XXS against the admin bot.
Before crafting an XXS, I needed to know what we want the bot to do. Looking at the Dockerfile we see that flag.txt is copied to the root of the Linux instance, and somehow we need to read this file.
Zip slip
One endpoint of interest is /firmware/upload, which requires admin access. It takes a file from a POST request and calls extract_firmware().
extract_firmware() does the following:
Copies a tar.gz file to the /tmp directory.
Extracts the tar.gz.
Copies the extracted files to a randomly-generated directory in static/firmware_extract.
This sounds like a zip slip. Something interesting is that tar.gz files allow you to tar existing symlinks and have them maintain their link when untarred on a different system. If we tar a file that is symlinked to /flag.txt, we can zip slip this file into the static folder on the challenge instance (which is publicly accessible) and then read it from there.
I used a tool called evilarc to generate the zip slip tar.gz file with the following command:
The XXS
I took the base64 of this file and crafted an XXS injection to send a POST request using fetch. This will trick the bot into uploading this file.
I used the following XXS injection and submitted this as a firmware report.
Once the bot visits the review page, it will use its admin credentials to upload our tar.gz file to the /firmware/upload endpoint. The Flask code will then attempt to un-tar the file, and our zip slip will put the symlink into /app/application/static/firmware_extract on the challenge filesystem.
We can then navigate to the endpoint /static/firmware_extract/symlink and flag.txt will be downloaded onto our computer.
Final script
I wrote this Python 3 script that does all the steps: