Server Side Template Injection in Flask with Mongodb
Server Side Template Injection in Flask with Mongodb — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) occurs when an attacker can inject template code that is subsequently executed by the server-side rendering engine. In Flask, this typically involves Jinja2. When a Flask application using Jinja2 templates incorporates user-controlled data into template logic without proper sanitization, and that data is also stored or queried in Mongodb, the combination can lead to unsafe behavior both at the template layer and at the database interaction layer.
Consider a Flask route that accepts a user-supplied name parameter, stores it in Mongodb, and later uses it within a Jinja2 template. If the input is not validated or escaped, an attacker can supply a payload such as {{ config }} or {{ ''.__class__.__mro__[1].__subclasses__() }}. Because the application later renders user-influenced data as part of the template, the injected code executes in the context of the server process. Meanwhile, the same user input may be written to a Mongodb collection via a Flask route using pymongo. If the application later retrieves that document and directly interpolates fields into a template, the stored payload can be re-triggered, creating a persistent injection vector.
The interaction with Mongodb becomes significant in two ways. First, if user input is stored unsanitized and later used to construct queries or template strings, the database effectively acts as a persistence mechanism for malicious template content. Second, although Mongodb itself does not execute templates, the application logic that reads from Mongodb may feed retrieved data directly into Jinja2, bypassing input validation that was applied at ingestion time. This can lead to sensitive information disclosure, such as accessing the Flask app’s configuration via {{ config }}, or enabling more severe attacks if the application deserializes or evaluates data from Mongodb without strict type checks.
Real-world patterns include a Flask endpoint that writes { 'username': request.args.get('username') } to a Mongodb collection, and then renders a profile page by passing the stored document into render_template. If the username contains Jinja2 syntax, the template engine processes it. Additionally, if the application uses eval-like behavior on data from Mongodb (even inadvertently via dynamic attribute access), the attack surface expands further. This specific stack does not mitigate SSTI with server-side protections unless explicit escaping, strict schema validation, and separation of data from template logic are enforced.
Mongodb-Specific Remediation in Flask — concrete code fixes
Remediation focuses on preventing user-controlled data from influencing template evaluation and ensuring safe handling of data retrieved from Mongodb. Do not serialize or interpolate raw user input into Jinja2 templates. Use Flask’s built-in escaping mechanisms and validate/sanitize data at the point of entry and before database operations.
Safe data storage and retrieval from Mongodb
Store only validated, minimal data in Mongodb and keep template logic separate from data. Use a schema-aware library or manual checks to ensure stored fields conform to expected types.
from flask import Flask, request, render_template
from pymongo import MongoClient
import re
app = Flask(__name__)
client = MongoClient('mongodb://localhost:27017')
db = client['api_db']
users = db['users']
@app.route('/profile')
def profile():
username = request.args.get('username', '')
# Validate: allow only alphanumeric and limited special characters
if not re.match(r'^[a-zA-Z0-9_\-\.]{3,30}$', username):
return 'Invalid username', 400
# Safe insert: store the validated username
users.update_one({'username': username}, {'$set': {'username': username}}, upsert=True)
# Retrieve the document
user_doc = users.find_one({'username': username})
# Pass only data, not raw user strings, to the template
return render_template('profile.html', username=user_doc.get('username') if user_doc else '')
Template best practices to avoid injection
Ensure templates auto-escape variables by default. In Flask with Jinja2, autoescape is typically enabled for HTML templates. Never use |safe on user-controlled data.
<!-- profile.html -->
<h1>Profile</h1>
<p>Username: {{ username }}</p>
If dynamic template inclusion is required, use a strict allowlist instead of user input:
allowed_pages = {'home', 'about', 'contact'}
page = request.args.get('page', 'home')
if page not in allowed_pages:
page = 'home'
return render_template(f'{page}.html')
Additional measures
- Use Flask-WTF or structured validation for inputs rather than manual string checks where appropriate.
- Do not store raw template fragments or executable content in Mongodb fields that may be read back into rendering context.
- Apply principle of least privilege to the Mongodb connection used by Flask so that even if injection occurs, the database impact is limited.
These steps reduce the risk that data persisted in Mongodb can be used to influence template evaluation, helping to prevent both direct and stored SSTI in this stack.