We can see the previously discussed tuple being returned to us. Since we want go back to the root
object class, we’ll leverage an index of
2 to select the class type
object. Now that we’re at the root object, we can leverage the
__subclasses__ attribute to dump all of the classes used in the application. Inject `` into the SSTI vulnerability.
As you can see, there is a lot of stuff here. In the target app I am using, there are 572 accessible classes. This where things get tricky, and why the tweeted payload mentioned above doesn’t work. Remember, not every application’s Python environment will look the same. The goal is to find something useful that leads to file or operating system access. It is probably not all that uncommon to find classes like
subprocess.Popen used somehere in an application that may not be otherwise exploitable, such as the app affected by the tweeted payload, but from what I’ve found, nothing like this is available in native Flask. Luckily, there is capability in native Flask that allows us to achieve similar behavior.
If you comb through the output of the previous payload, you should find the
<type 'file'> class. This is the key to file system access. While
open is the builtin function for creating file objects, the
file class is also capable of instantiating file objects, and if we can instantiate a file object, then we can use methods like
read to extract the contents. To demonstrate this, find the index of the
file class and inject `` where
40 is the index of the
<type 'file'> class in my environment.
So, we’ve now demonstrated that arbirtrary file access is possible via SSTI in Flask/Jinja2, but we’re not done yet. My goal in this was Remote Code/Command Execution.
The previous article referenced several methods of the
config object that load objects into the Flask configuration environment. One such method was the
from_pyfile method. Below is the code for the
from_pyfile method of the
def from_pyfile(self, filename, silent=False):
"""Updates the values in the config from a Python file. This function
behaves as if the file was imported as module with the
:param filename: the filename of the config. This can either be an
absolute filename or a filename relative to the
:param silent: set to `True` if you want silent failure for missing
.. versionadded:: 0.7
filename = os.path.join(self.root_path, filename)
d = imp.new_module('config')
d.__file__ = filename
with open(filename) as config_file:
exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
except IOError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
e.strerror = 'Unable to load configuration file (%s)' % e.strerror
There’s a couple of interesting things here. The most obvious is the use of the
compile function against the contents of a file whose path is provided as a parameter. This would come in handy if we had a way to write files to the operating system, no? Well, as we just discussed, we do! We can use the aforementioned
file class to not only read files, but write them to world writeable locations on the target server. Then, we can call the
from_pyfile method through the SSTI vulnerability to compile the file and execute the contents. This is a 2 staged attack. First, inject something like
into the SSTI vulnerability. Then, invoke the compilation process by injecting. The code will execute upon compilation. Remote Code Execution achieved.
But let’s take it a step even further. While running code is great and all, having to go through a multi-step process for each block of code we want to run is tedious. Let’s leverage the
from_pyfile method for its intended purpose and add something useful to the
config object. Inject `` into the SSTI vulnerability. This will write a file to the remote server that, when compiled, imports the
check_output method of the
subprocess module and sets it to a variable named
RUNCMD, which, if you recall from the previous article, will get added to the Flask
config object virtue of it being an attribute with an upper case name.
Inject `` to add the new item to the
config object. Notice the difference between the following before and after images.
Now we can invoke the new configuration item to run commands on the remote operating system. Demonstrate this by injecting `` into the SSTI vulnerability.
Remote Command Execution achieved.
We can now close the book on escaping the Flask/Jinja2 template sandbox and conclude that the impact of SSTI in Flask/Jinja2 environments is substantial. I’d also like to point out that this is largely the result of the way Python works and not so much the fault of the Flask framework. I’d be willing to bet that all of the Python MVC/MTV web frameworks suffer from similar exploitation vectors. Ultimately, it is up to the developers using these frameworks to properly follow template design best practices and ensure that their applications do not blindly trust user-supplied data.
Update 04/06/2016: For a real world example, please reference this report to see how SSTI affected Uber.