SSTI in Flask/Jinja2

IndominusByte
6 min readDec 27, 2019

--

Photo by Markus Spiske on Unsplash

What is SSTI ( Server-Side Template Injection)

Server-Side Template Injection is possible when an attacker injects template directive as user input that can execute arbitrary code on the server. If you happen to view the source of a web page and see below code snippets then it is safe to guess that the application is using some template engine to render data.

Code vulnerable in a flask

The developer wants to echo back from request get which is named search and render to function call render_template_string it is based on the flask.

We can indicator possible SSTI by add {{ 7* 7 }} to the parameter search, we can see that the template engine evaluates the mathematical expression and the application responds with 49

What is interesting in SSTI In Flask

We make our first interesting discovery by introspecting the request object. The request object is a Flask template global that represents “The current request object (flask.request).” It contains all of the same information you would expect to see when accessing the request object in a view. Within the request, an object is an object named environ. The request.environ object is a dictionary of objects related to the server environment. One such item in the dictionary is a method named shutdown_server assigned to the key werkzeug.server.shutdown. So, guess what injecting {{ request.environ[‘werkzeug.server.shutdown’]() }} does to the server? You guessed it. An extremely low effort denial-of-service. This method does not exist when running the application using gunicorn, so the vulnerability may be limited to the development server.

Our second interesting discovery comes from introspecting the config object. The config object is a Flask template global that represents “The current configuration object (flask.config).” It is a dictionary-like object that contains all of the configuration values for the application. In most cases, this includes sensitive values such as database connection strings, credentials to third party services, the SECRET_KEY, etc. Viewing these configuration items is as easy as injecting a payload of {{ config.items() }}.

And don’t think that storing these configuration items in environment variables protects against this disclosure. The config object contains all of the configuration values AFTER they have been resolved by the framework.
Our most interesting discovery also comes from introspecting the config object. While the config object is dictionary-like, it is a subclass that contains several unique methods: from_envvar, from_object, from_pyfile, and root_path.

Inject from config subclass and the true impact of SSTI

The from_object method then adds all attributes of the newly loaded module whose variable name is all uppercase to the config object. The interesting thing about this is that attributes added to the config object maintain their type, which means functions added to the config object can be called from the template context via the config object. To demonstrate this, inject {{ config.items() }} into the SSTI vulnerability and note the current configuration entries.

Then inject {{ config.from_object(‘os’) }}*. This will add to the config object all attributes of the os library whose variable names are all uppercase. Inject {{ config.items() }} again and notice the new configuration items. Also, notice the types of these configuration items.

Any callable items added to the config object can now be called through the SSTI vulnerability. The next step is finding functionality within the available importable modules that can be manipulated to break out of the template sandbox.

Disclaimer before exploit

The MRO in __mro__ stands for Method Resolution Order, and is defined [here] as, “a tuple of classes that are considered when looking for base classes during method resolution.” The __mro__ attribute consists of the object’s inheritance map in a tuple consisting of the class, its base, its base’s base, and so on up to object (if using new-style classes).

The __subclasses__ attribute is defined [here] as a method that “keeps a list of weak references to its immediate subclasses.” for each new-style class, and “returns a list of all those references still alive.”

Greatly simplified, __mro__ allows us to go back up the tree of inherited objects in the current Python environment, and __subclasses__ lets us come back down. So what’s the impact on the search for a greater exploit for SSTI in Flask/Jinja2? By starting with a new-type object, e.g. type str, we can crawl up the inheritance tree to the root object class using __mro__, then crawl back down to every new-style object in the Python environment using __subclasses__. Yes, this gives us access to every class loaded in the current python environment. So, how do we leverage this newfound capability?

Exploitation SSTI

The first thing we want to do it is to select a new-style object to use for accessing the object base class. We can simply use ‘ ‘, a blank string, object type str. Then, we can use the __mro__ attribute to access the object’s inherited classes. Inject {{ ‘’.__class__.__mro__ }} as a payload into the SSTI vulnerability.

We can see the previously discussed tuple being returned to us. Since we want to go back to the root object class, we’ll leverage an index of 1 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 {{ ‘’.__class__.__mro__[1].__subclasses__() }} into the SSTI vulnerability.

As you can see, there is a lot of stuff here. In the target app, I am using, there are more than 100 accessible classes.. this where things get tricky. 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 somewhere 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 a capability in the native Flask that allows us to achieve similar behavior.

Luckily we got class subprocess.Popen and to search where a specific index is subprocess.Popen we can use slicing in python. Inject again and search where index subprocess.Popen {{‘’.__class__.__mro__[1].__subclasses__()[284:]}}.

Finally we found the exactly inject {{‘’.__class__.__mro__[1].__subclasses__()[287]}} where 287 is the index of the *<class ‘subprocess.Popen’> class in my environment. Now we can exploit using subprocess by adding some malicious code.

And now we can fully control the web application.

References:

--

--