Secure Templating with Jinja2: Understanding SSTI and Jinja2 Sandbox Environment

Control Jinja2 SSTI through its SandboXEnvironment

Jinja2 is a popular templating engine used in Python web applications. It provides a powerful and flexible way to generate dynamic HTML, XML, and other output formats. However, as with any templating engine, it is vulnerable to template injection attacks, where an attacker can inject malicious code into the template, which is executed when the template is rendered.

Example of Jinja2 SSTI

Suppose you have a web application that allows users to input their names and then displays a welcome message using a Jinja2 template. The template code might look something like this:

Welcome, {{ user_name }}!

If the user enters their name as {{ 2+2 }}, the resulting template code will be:

Welcome, {{ 2+2 }}!

Jinja2 will evaluate the expression 2+2 and substitute the result into the template, resulting in:

Welcome, 4!

This behavior is expected and harmless. However, if an attacker submits malicious code as their name, they can execute arbitrary code on the server. For example, if an attacker submits their name as {{ ''.__class__.__mro__[1].__subclasses__()[238]('/etc/passwd').read() }}, the resulting template code will be:

import jinj2 
env = jinja2.Environment()
template1 = "{{ ''.__class__.__mro__[1].__subclasses__()}}"
print(env.from_string(template1).render())

template2 = "{{ ''.__class__.__mro__[1].__subclasses__()[238] ('/etc/passwd').read() }}". # 238 could be different index in your environment

print(env.from_string(template2).render())

The resulting template will render all the classes loaded and with that, you can run file operations.

Solution

One way to mitigate this risk is by using the SandboxedEnvironment in Jinja2. The SandboxedEnvironment is a feature of Jinja2 that allows you to create a restricted environment for executing templates.

The main purpose of the SandboxedEnvironment is to limit the capabilities of the template to prevent it from executing arbitrary code. This is useful in situations where you want to allow untrusted users to create templates, but you don't want to give them access to potentially dangerous Python code.

Here's an example of how to use the Jinja2 sandbox environment to evaluate an expression in a secure way. Let's try using the same template that we tried earlier.

from jinja2.sandbox import SandboxedEnvironment

env = SandboxedEnvironment()
template = "{{ ''.__class__.__mro__[1].__subclasses__()}}"

rendered_template = env.from_string(template).render()

print(rendered_template)

This code snippet when executed will output

jinja2.exceptions.SecurityError: access to attribute '__class__' of 'str' object is unsafe.

In addition to running a default sandboxed environment, you can also deny access to specific functions and modules by creating a custom Environment subclass and override the is_safe_attribute method.

Here's an example:

from jinja2.sandbox import SandboxedEnvironment

class MyEnvironment(SandboxedEnvironment):
    def is_safe_attribute(self, obj, attr):
        if attr in ['os', 'subprocess', 'eval', 'exec']:
            return False
        return super().is_safe_attribute(obj, attr)

env = MyEnvironment()

Jinja2 Sandboxed Environment is a powerful security feature that can help protect your web application against template injection attacks. By restricting access to certain Python modules, functions, and filters, the Sandboxed Environment prevents untrusted code from accessing sensitive resources on the underlying system.

References

https://jinja.palletsprojects.com/en/3.0.x/sandbox/

Your engagements are welcome. I talk about backend tech and frameworks, for more such articles subscribe to my blog.