Skip to content

SSTI

Server side template injection in where an attacker is able to use template syntax to inject a malicious payload into a web template, this template is then executed server side.

Template Engines

Having a template engine to generate your web page is a brilliant idea, it allows you to use the power of the underlying programming language to help build your pages, without resorting to a either generating chunks of rendered code by hand, or a fully "dynamic" JavaScript / XHR style solution.

They also help us to be "Good Programmers(TM)" and do all that MVC style separation of concerns stuff. This is good, as being a coder doesn't necessarily mean you are good at the design elements1 (and vice-versa), so you can let the design mooks build what the site looks like (and get excited about mobile first and UX), and leave placeholders for the backend bods to do their magic with the data.

For example, suppose we wanted a alert box that displayed the users name, our template could look something like this.

<div class="alert">
    Hello {{ user }} 
</div>

We then provide the template with provide it with the value for user (for example user = dang) which is substituted when the template is rendered.

The Problem with flexibility

While this works well for single, well specified variables, what happens when we have more complex data? How do we handle things like lists? What happens for variables that may only exist in a given situation.

We could move the formatting into the backend code, supplying a fully formed list to the template engine. But this negates all of the advantages of templates. We worked hard to remove some of the presentation logic from the coders, but still trust them with things like lists.

The more sensible alternative is to give the template engine itself some limited processing power. It makes sense that we can keep the presentation logic separate by allowing simple calculations, selection and iteration in the template itself.

For example, we could build and display list of users preferences like this:

<ul>
{% for item in preferences %}
  <li>{{ item }}</li>
{% endfor %}
</ul>

While this is an sensible way to give the templates the functionality needed to separate program logic and presentation. It leaves us with a whole new issue. How much functionality do we want to push to the template, and how do we filter potentially insecure input.

Security is often a trade-off. We have lock down functionality to increase security, but this restricts what we can do. In the case of templates, completely stopping the template from executing code would increase security, but at the expense of usefulness. We could stop things like iteration, but this then pushes the view logic back to the controller breaking our MVC pattern.

So we have to trust our developers to manage some risk in return for convenience. Giving the template engine some of the powers of the underlying interpreter, makes the tool more useful. However, the devs need to accept that it also comes with potential security drawbacks.

The template engine itself may also have some protection against this type of exploit. This is usually though filtering input though allow or deny lists, or running code through some form of sandbox that restricts the interpreters system access. However, as with any filtering or sandbox based approach, this is only as successful as the assumptions made when developing the filter. We will look at some of the methods to work around these features later.

On the whole the template based system works well, the vast majority of template based systems wont have a security flaw. However, if the dev tries to do something unusual, or passes un-sanitised user input to the engine, problems can occur (as usual, trusting the user is a bad idea).

Executing code within a Flask / Jinja template.

Having looked at the trade-off between functionality and security in template engines, lets see how we can use this to get unexpected behaviour.

Imagine we have an application that takes user input from a form. For example:

<form>
    <input name="payload">
    <button class="submit">Go</button>
</form>

A "Safe" version

Now lets imagine our flask code is simply takes the variable and passes it to a template Something along the lines of:

theTemplate = """
<html>
<form>
  <input name="payload">
  <button class="submit">Go</button>
</form>

 <p>User Input is {{ userinput }}</p>
</html>
"""

@app.route('/')
def main():

    payload = flask.request.args.get("payload")

    return flask.render_template_string(theTemplate, userinput = payload)

!!! note:

NOTE: To keep the examples similar I am building the template from a string, its the
same if you use a file)

Now this should2 be safe and behave in the way we want, Our user can specify an input, and the same input is escaped, and passed to the template engine. There is even some protection against XSS2, as tags like < are escaped by the template engine.

An Unsafe version

To see an unsafe version we can make a small modification to the template. This time we are going to use pythons string formatting syntax to add the user input.

unsafeTemplate = """
<html>
<form>
  <input name="payload">
  <button class="submit">Go</button>
</form>

 <p>Unsafe Input is {}</p>
</html>
""".format(payload)

Again, I like format strings so am using them, we get the same effect with f-strings, or string replace functions.

  • If we use a payload of 1+1 we get our expected output of Unsafe Input is 1+1.
  • If we use the an input of {{ 1+1 }} which gives us an output of Unsafe Input is 2.

So whats going on here? It turns out we are being bitten by the flexibility of the template engine. When we specify an input of {{ 1 + 1}}} the template that gets created and passed to the engine becomes

<html>
  ... snip ...
 <p>Unsafe Input is {{ 1 + 1 }}</p>
</html>

Now the {{ 1+1 }}} is a valid template substitution string, Jinja has no idea (and doesn't really care as it trusts you to do the right thing) if its user input or not. Thus it sees the string, checks it knows what to do with it (yes it knows how to add two numbers together), so evaluates it and returns the output.

So our small logical error, using the wrong form of string replacement, has given us a potential security issue. (its also given us an XSS problem, but that's another story)

If you are wondering is this would work with our safe example, the answer is no. I haven't actually dug around in the internals here, but it looks like the string escape on user input keeps us safe. So if we enter 1+1 in the form for the safe version, what gets passed to the template is "1+1"

Detecting SSTI

We can take a Fuzzing approach to detecting SSTI. Taking a set of standard payloads to see how the server responds.

For example we know that Flask / Jinja will eval python code so our input of {{ 7+7 }} will return 14.

For a detailed list of different fuzzing payloads see Payloads all of the Things

We can also use the result of the input to help us understand the types of technology we are dealing with

For example:

  • {{ 7*7 }} Will return 49 in several template engines
  • {{ 7*'7'}} Will return:
    • 49 in Twig
    • "7777777" in Jinja.

As SSTI executes the templates on the server side, understanding the technologies used there is vital to exploiting the server.


  1. You are looking at an example of this. 

  2. For a given value of should, It seems secure against some most prodding, but things are only secure until they aren't. 

Back to top