Secure Templating with Jinja2: Understanding SSTI and Jinja2 Sandbox Environment
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.
Subscribe to my newsletter
Read articles from satish Mishra directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
satish Mishra
satish Mishra
software engineer with over 10 years of experience designing, developing, and leading enterprise and cloud-native applications. With a specialization in cybersecurity and infosec domains, I have SOAR, IPaaS, and business workflow automation expertise.