Server Side Template Injection in Flask with Dynamodb
Server Side Template Injection in Flask with Dynamodb — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) occurs when an attacker can inject template code that is rendered by the server-side templating engine. In Flask, this typically involves Jinja2. When a Flask application builds DynamoDB requests using user-controlled input that is later rendered in a template, SSTI can lead to unintended code execution or data leakage through the template context.
Consider a Flask route that accepts a query parameter and passes it to a DynamoDB scan operation, then renders the results in a Jinja2 template:
from flask import Flask, request, render_template_string
import boto3
app = Flask(__name__)
@app.route('/search')
def search_users():
username = request.args.get('username', '')
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('users')
response = table.scan(
FilterExpression=boto3.dynamodb.conditions.Attr('username').eq(username)
)
items = response.get('Items', [])
return render_template_string('''
Users
{% for user in items %}
- {{ user.username }} — {{ user.email }}
{% endfor %}
''', items=items)
At first glance, the DynamoDB call appears safe because the input is used only in a filter expression. However, if the developer mistakenly passes username directly into the template context or uses string formatting to build the template, SSTI becomes possible. For example, using Markup or concatenating user input into the template string enables Jinja2 to execute injected code:
@app.route('/dangerous')
def dangerous():
username = request.args.get('username', '')
# Dangerous: injecting user input directly into template source
template = 'Hello ' + username
return render_template_string(template)
An attacker could provide a payload such as {{7*'7'}} or {{config.__class__}} to achieve arbitrary code execution or sensitive data exposure. Even when DynamoDB is not directly involved in the template, SSTI can expose internal application state, configuration, or AWS credentials if they are present in the Flask app context or global variables.
Because middleBrick scans test unauthenticated attack surfaces across multiple categories including Input Validation and SSRF, it can detect SSTI vectors that involve template rendering, even when DynamoDB is used as the backend datastore.
Dynamodb-Specific Remediation in Flask — concrete code fixes
Remediation focuses on strict input validation, avoiding string-based template construction, and ensuring user data never reaches the template engine in an executable form. Use parameterized queries for DynamoDB and keep templates static with explicit context variables.
1. Use safe DynamoDB filter expressions with type-checked attributes
Always construct filter expressions using the DynamoDB Condition API rather than string interpolation. Validate and sanitize input before using it in database operations:
from flask import Flask, request, render_template
import boto3
from boto3.dynamodb.conditions import Attr
import re
app = Flask(__name__)
@app.route('/search-safe')
def search_users_safe():
username = request.args.get('username', '')
# Allow only alphanumeric and limited special characters
if not re.match(r'^[a-zA-Z0-9_.-]{1,50}$', username):
return 'Invalid username', 400
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('users')
response = table.scan(
FilterExpression=Attr('username').eq(username)
)
items = response.get('Items', [])
# Pass only plain data to the template
return render_template('results.html', items=items)
2. Use explicit template context and disable autoescape exploits
Ensure templates do not receive dynamic template source. Use explicit variable names and avoid marking user input as safe:
# results.html (static template file)
<h1>Users</h1>
<ul>
{% for user in items %}
<li>{{ user.username | e }} — {{ user.email | e }}</li>
{% endfor %}
</ul>
The | e filter escapes HTML, preventing injection through rendered output. Never use render_template_string with user-controlled strings.
3. Protect application context and AWS credentials
Ensure no sensitive objects (e.g., DynamoDB client, app config) are exposed to the template context. Flask’s app.jinja_env.globals should remain minimal:
# Avoid adding sensitive objects to Jinja globals
# app.jinja_env.globals.update(aws_secret=my_key) # Do not do this
middleBrick’s checks for Data Exposure and Input Validation can help verify that sensitive data and executable template constructs are not unintentionally surfaced.