Server-side template injection (SSTI) - Jinja2

Web applications often use server-side template technologies and in this example we will use the Jinja2 template engine. Jinja2 is a templating language for Python and is used to generate dynamic HTML rendering. Server-side template injection (SSTI) vulnerabilities occur when trusted user input is added to content that is later processed by a template rendering process.

This Python code is a part of a web application and uses the Flask framework and Jinja2 as it's template engine.

  • The search variable in the code takes the value provided by the client in the GET parameter named: search.
  • The content variable contains the HTML code that will be displayed to the client. The syntax {{ ... }}is used to later tell Jinja2 that this value should be replaced with the value given to the variable domain. The %s marker is used to insert the client's value provided by the search parameter directly into the content.
  • Finally, Python uses the render_template_string() function which uses Jinja2 to replace the {{ domain }} with the given value, then return the content as the HTTP response body to the client.

The issue here is that the keyword from the user is inserted before the Jinja2 is inserting it's given values. In case the client insert something like {{ 7*7 }} Jinja2 would see the content string as following:

Jinja2 will now see both {{ domain }} and {{ 7*7 }}. This causes Jinja2 to replace both these template markers with the requested values as follows:

Since we have control over the GET parameter search that is inserted into the HTML content before the template engine (Jinja2) renders the content, a template injection vulnerability exists.
We can try inject a payload like: {{ self.__init__.__globals__.__builtins__ }}

These payloads provide the result of built-in functions, types, exceptions, and other objects that the application's Python code uses. We can take advantage of them by finding an "exception" like import. With this "exception", we can import a library like os that will help us execute system commands on the target application, resulting in a remote code execution (RCE).
Payload : {{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}

We can see that we managed to execute a system command on the application, which resulted in a remote code execution.

If you want to learn more about Python Jinja2 and how to defeat protections you can take a look at our practical challenge: