Writeup by r2uwu2
Upload your files on the Kalmar File Store and share them with your friends.
We are given a relatively simple flask application.
We can upload an image by clicking Upload Image
.
We can then view the image by clicking on the image.
In this application, we have one endpoint to see all the files in our session and another endpoint to upload files to our session. Our flag is in /flag.txt
and we must exfiltrate it.
The session id is the value of the session
cookie in flask. It is well-known that in frameworks like flask, if we can write to the templates
directory, we can perform a Server-Side Template Injection (SSTI) and achieve arbitrary code execution. I crafted a payload to send, but was met with an error:
Unfortunately, we are not able to craft a malicious template because our user does not have the permissions to do so.
As declared in the Dockerfile
, we run as the ctf
user who only has write access to static/uploads
and flask_session
directories. static/uploads
is where the images are uploaded and flask_session
contains session information.
Since I am able to upload to static/uploads
, I tried uploading an xml file to perform a local file inclusion via XXE. The first example in the link is a test for whether the server is vulnerable to XXE.
If the server is vulnerable to an XXE, &example
will be substituted with the ENTITY
definition. This would indicate that we have the ability to read a file as we can set an entity to a file we want to read.
However, we get this reponse back:
Unfortunately for us, flask's xml handling does not expand out ENTITY
so we will not be able to LFI.
Other than static/uploads
, we can overwrite all the flask_session/*
session objects. I ran a bash shell in the docker to experiment around.
Looking around in the directory, we have a file for each session. The file name does not match the session id. Each session seems to be some form of serialized object. I decided to look at flask-session
source code.
The filesystem session seems to use cachelib library as indicated by below code:
Looking into cachelib code, the cache is fairly suspicious:
It seems like if we set the first 4 bytes of the filename to 0
, it decides it's pickle time and unpickles the pickle (one can see explicit pickle calls in other files).
If we snoop around the cachelib
codebase, we can see that the filename is generated by md5
hashing the key name. We can now overwrite arbitrary sessions.
It's pickling time woo.
Our overall flow looks like:
owo
by uploading a picture (let's make it a picture of alex typing for extra hacking powers)../../flask_session
and upload a pickle with file name md5("owo")
- the pickle copies /flag.txt
to /app/static/uploads
so we can read it laterowo
to load our malicious pickleGET /static/uploads/flag.txt
request and acquire the flagThe flag has now been captured