PicoCTF 2018 Writeup: Flaskcards And Freedom
30.01.2019The problem:
"There seem to be a few more files stored on the flash card server but we can't login. Can you?
http://2018shell.picoctf.com:52168
"
This is an interesting challenge. It allows you to exploit a server-side template injection bug in Flask's template engine, Jinja2, to gain remote code execution on the server.
There is not much you can do on the initial page. It looks like your only option is to create an account. So click that option and register with a username and password.
This lets you sign in with the newly crated user. Once signed in, you are presented with new options. Click "Create Card". This is where the bug is located. Create a new card where both fields are set to the string {{ 1 + 1 }}
.
Click "Create". Then navigate to "List Cards". Notice that the card no longer shows the string we put in. It evaluated the code to be 2.
Now we have to find out how to do something useful with this. We are able to execute our own python code inside the scope that the template engine has. For instance, we can access the config
variable by inputting the string {{ config }}
. This is interesting, as it allows us to sign our own cookies using the secret inside the config dict, but for this challenge, we need to do more.
To solve this challenge, we need to obtain a shell, not only python code execution inside of the Flask application.
We can use some python hacks to achieve this. Our goal is to import the class os
. We want to use the function popen
to access the shell. But because this module is not imported in the Flask app, we need to start with a class that is definitely imported. We will use []
. From there, we access its base class, which should be object
. Because all classes derive from object
, we can list all the imported classes through the function __subclasses__()
.
![firstexp.png](https://blog.hanneshertach.com/media/view/firstexp.png)
So all together, put the code into a new card {{ [].__class__.__base__.__subclasses__() }}
. Submit the card, and view it. You should get a list of all available classes.
In this massive list, you want to look for a class named flask.config.ConfigAttribute
, and note its index in the list. This class allows us to use its __init__
method to access the global function eval
. eval
lets us execute any python code we wants - even things that the template language would block.
So the code I have so far is [].__class__.__base__.__subclasses__()[101].__init__.__globals__["__builtins__"]["eval"]
. Note that the index 101 might vary. If the code does not work for you, you can try the numbers around 101 to see if one of those classes matches the one we want. Now that we have the eval function, the rest is easy. Simply import os
, and call popen
. At first we want to list the files in the directory. Put the following code into a new card:
{{ [].__class__.__base__.__subclasses__()[101].__init__.__globals__["__builtins__"]["eval"]("__import__('os').popen('ls').read()") }}
This will show us all of the files in the current directory. One of the files is `flag`. So lets read the contents of `flag` by adding a final card with the same exploit code, but with `cat flag` instead of `ls`. So the final exploit code is:
{{ [].__class__.__base__.__subclasses__()[101].__init__.__globals__["__builtins__"]["eval"]("__import__('os').popen('cat flag').read()") }}
And there's our flag!
A quick note on debugging: If at any point your Python code has a syntax error, or can't find one of the classes, it will output the following message:
Simply start deleting statements from the right hand side of your exploit code until it starts working again, and then debug and work your way up from there.