Tags: web

Rating:

In this task, you had to upload a valid gif to a webservice, and somehow get a flag with that. What did the app do? It was running ffmpeg on uploaded gif and breaking it into frames saved as .png files in uploads/{random name}/ folder. You could preview these images, the random name was known. Code was public. The checks for validity of a file were:

1. File exists
2. File has an extension of ".gif"
3. Content type is image/gif
4. File name consists only of allowed chars check by regex ^[a-zA-Z0-9_\-. '\"\=\$\|]*$  and does not have a ..
5. File mimetype is image/gif

Flag was stored as a variable in main script (main.py), but never used.

Most important part of the task was this line inside main.py:

command = subprocess.Popen(f"ffmpeg -i 'uploads/{file.filename}' \"uploads/{uid}/%03d.png\"", shell=True)

in which ffmpeg in a shell was run. It was obvois, that we can somehow manipulate the filename, so that we can execute our own commands.

After lots of offline trials with the filename, such one was made:

nosuchfilelol.gif' \"lol.png\" || grep ffLaG $(find$PWD -maxdepth 1 -type f -name main.py) | tee 'lulz.gif

Yes, this line above is a filename! I used Burp Suite and Intercepting tool to pass it. GIF that i was uploading was completely legit.

When putting it inside the shell command, the server executed something like this:

 ffmpeg -i 'uploads/nosuchfilelol.gif' "lol.png" || grep ffLaG $(find$PWD -maxdepth 1 -type f -name main.py) | tee 'lulz.gif' "uploads/{uid}/%03d.png" 

That is happening in here? First, we pass nonexisting gif to ffmpeg , which crashes, so it triggers the secont part of exploit, after the || (OR) sign. The second part triggers the find command on current folder (\$PWD) to "find" main.py. This way, we can bypass the problem of getting the full path to main.py (regex did not allow slashes). Next, we grep inside the file for flag, and we pass it to tee command, which saves it into two files (first one is of course useless, but the filename had to finish with .gif). Now, we just read the contents of uploads/{random name}/%03d.png and boom! We got the flag.

Note:
To read the %03d.png file we had to URL-encode it, so the GET request was like uploads/{random name}/%2503d.png 

Good CTF!