Slippery Upload — 247CTF
Today I encountered an interesting WEB challenge on 247CTF, so I would like to share with everyone. The challenge’s name is “Slippery Upload”
Looking at the title, we may know that it’s some kind of upload vulnerability. Start the challenge and navigate to the website:
We can see the source code of “/app/run.py”, which is the website itself. There’s 1 route to “/zip_upload”, and this route only accepts “POST” method. The “zip_upload()” method allow us to post a zip file. It will perform check on the upload filename and content-type before uploading it to server and extracting using “zip_extract()” method.
Looking carefully at “zip_extract()” , the last 2 lines of this function indicate it’s vulnerable to Zip Slip vulnerability. You can read more about it on Snyk.
Zip Slip is a form of directory traversal that can be exploited by extracting files from an archive. The premise of the directory traversal vulnerability is that an attacker can gain access to parts of the file system outside of the target folder in which they should reside. The attacker can then overwrite executable files and either invoke them remotely or wait for the system or user to call them, thus achieving remote command execution.
Normally, the uploaded zip file will be extracted to ‘UPLOAD_FOLDER’, which is “/tmp/uploads”. But what if the zip file’s name is “../../evil.sh”? -> The ‘evil.sh’ will be extracted to ‘/’ directory (as we move up 2 level).
Knowing the vulnerability, I’ll make a malicious zip file to upload and test. A simple python script will help to create the zip file:
Let’s run the code:
I’ve had my zip file created. Open this zip file:
Pay attention to “Location”. You can see the “test.txt” is followed by “../../app” .
Now let’s upload it to the website using curl:
The zip file has been uploaded successfully! Now we can confirm the Zip Slip vuln.
So let’s find a way to get RCE. As we know, the website itself is the “run.py” in “/app”. What if we can modify the “run.py” and add our own code? Let’s try!
Using the previous script, but this time, instead of “test.txt”, it will be “run.py”:
Create the zip file, then upload using curl again:
It has been uploaded! Reload the webpage:
As you can see, the website now cannot load, because there’s only “Test” in “/app/run.py”! Which means, it has been modified. So now, you have to stop and start the challenge again to reset it. After that, instead of writing only “Test” to our “run.py”, let’s copy the whole original code and paste into it, but of course add our own code to gain RCE.
I’ve modified my create zip script. Now, it will read the content of “src_new.py” and zip it. “src_new.py” contains original “run.py” code, along with my own code to execute command on server. Below is content of “src_new.py”:
You can see, I’ve added a comment and some code to execute “cmd” when I route to ‘/exec’. Again, run the code, and upload the zip file.
Upload finished. Let’s reload the webpage:
The content has been changed to my own “run.py” code.
So now, let route to “/exec” and execute some Server-Side template injection.
As SSTI is executed, you can use some SSTI payload of Jinja to gain RCE. For example, the following payload execute “id” command by using “os” module:
You can see above, the command “id” has been executed. So, just play with some “ls” and “cat” to get the flag!
In order to prevent such kind of vulnerability, use “ZipFile.extract()” method, because:
If a member filename is an absolute path, a drive/UNC sharepoint and leading (back)slashes will be stripped, e.g.:
foo/baron Unix, and
foo\baron Windows. And all
".."components in a member filename will be removed, e.g.:
foo../ba..r. On Windows illegal characters
(:, <, >, |, ", ?, and *)replaced by underscore
based on ZipFile Documentation.