TU Delft CTF 2024 - Script Runner
On the 5th of October 2024, my team and I attended the TU Delft CTF 2024. Our team won the 3rd price with a total of 5403 points. In total, 51 teams attended the CTF with each 4 members.
It was a fun CTF and organized well too. Props to the organization!
Write up Script Runner
Script Runner is a web challenge that was rated the hardest of the web challenges by the organizers. In total, 4 teams were able to solve this challenge.
Summary
Script Runner hosts a service to run scripts that you upload! Sounds easy right? The catch here is that it checks the hash of your uploaded file, stores it in a static folder and compares it to a bunch of secure hashes, that are stored in a local file. If your uploaded file/script doesn’t match any of the local hashes, it doesn’t execute it.
We are given the source code of the application to analyze any vulnerabilities in the upload and execute process. We find a Race Condition vulnerability between the instruction that uploads our file the instruction to check if the file matches the secure hashes.
We are able to exploit the vulnerability by sending a correct file first that will be stored locally. We have to wait a little bit for the check instruction to be completed and marks our file as safe. Then we overwrite that same file with our own payload, which will then execute the file with our own payload in it.
Understanding the application
In order to find any vulnerabilities, we have to first understand the application and its functionality. We are given the soure code with a Dockerfile. We start by running the application locally.
We can tell that it is a simple upload form. The application tells us that we are only allowed to run the three specified scripts (date.sh, fortune.sh, sus.py). Let’s run date.sh first.
1 |
|
The output shows us the stdout of the executed shell command, good. Let’s try to inject our own command instead of “date”. We will do this by intercepting the request using BurpSuite. We modify the date command to execute id instead.
Then, we forward the request. The result is that we are not allowed to run the script:
Source code analysis
To identify vulnerabilities, we can try all such of things from a blackbox perspective, but if you have any source code at your disposal, it is always a good idea to analyze and understand it. Let’s read the app.py first. The only functionality seems to be the /upload route of the Flask application:
1 |
|
The application seems to calculate a hash of our uploaded file and then accepts/denies it based on the outcome of that hash. Let’s take a look at the is_allowed function of utils.py:
1 | # Calculate hash, accepts argument to filepath |
The “insecure_hash” variable calculates a SHA256 hash of our input file. Theoretically, this should already be secure. But then a secure_hash variable is defined that makes it even more secure. There is noway that we can create a hash collision.
Then we should look for other options. What about overwriting the hashes.txt with our own hashes? Well if we remember, our input is being sanitized by this “secure_filename” function:
1 | # Sanitize the file and store it |
This function is being imported from werkzeug utils.
1 | from werkzeug.utils import secure_filename |
This function is considered safe since it correctly removes any “/“ or other characters that lead to path traversal. So the option to overwrite hashes.txt seems to be off the table.
Identifying a Race Condition vulnerability
If we take a closer look at the order of the instructions that upload, check and execute our input script, we can see something is not logical.
1 | # Sanitize the file and store it |
The sanitize function saves our input locally before checking if it is secure. In theory, we can upload a safe file (the date.sh for example) and overwrite that same file. At the time that the script is being executed, we must overwrite it, but not before the is_allowed check function is being executed.
The code underneath explains how we can exploit this Race Condition vulnerability:
1 | # Sanitize the file and store it |
Exploiting the Race Condition vulnerability
Now that we have found the vulnerability, we need to exploit it successfully. In order to do so, we intercept the request of date.sh using Burp Suite and send it to the repeater. We leave the safe file untouched.
Next, in Burp Suite, we create a group of tabs called “Race Condition”:
This results in:
In Burp Repeater, clone the safe request with date.sh to tab 2. Modify the input to your own script, for example, let’s modify it to execute “id”:
1 | -----------------------------32292399912690620422393530341 |
In Burp Suite, right click on this modified request tab and duplicate it 10 times:
Finally, select “Send group (parallel)” instead of “Send”:
This will send our entire group of 12 tabs in parallel. Send the requests. The result is that all of the duplicated tabs result in the error code “Script not allowed!” But the request with “date” in it now executed our script:
We have a PoC. Now we can run it remotely and get the flag from /flag.txt