# Jinja2 SSTI via Untrusted Template Rendering

Language: Python
Severity: Critical
CWE: CWE-94

## Source
9

## Flow
9-10-11-12

## Sink
12

## Vulnerable Code
```python
from flask import Flask, request
from jinja2 import Template
import boto3

app = Flask(__name__)
s3_client = boto3.client('s3')

@app.route('/cloud/report/generate', methods=['POST'])
def generate_cloud_report():
    bucket_name = request.form.get('bucket')
    report_format = request.form.get('format_template', 'default')
    resource_count = s3_client.list_objects_v2(Bucket=bucket_name).get('KeyCount', 0)
    report_template = request.form.get('custom_header', 'AWS S3 Bucket Analysis')
    template_str = f"<html><head><title>{report_template}</title></head><body><h1>{{{{ title }}}}</h1><p>Resources: {{{{ count }}}}</p></body></html>"
    jinja_template = Template(template_str)
    rendered_output = jinja_template.render(title=report_template, count=resource_count)
    return rendered_output, 200, {'Content-Type': 'text/html'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)
```

## Explanation

The application takes user-controlled input from 'custom_header' parameter and directly embeds it into a Jinja2 template string using an f-string. This user input becomes part of the template itself before being rendered, allowing attackers to inject malicious Jinja2 template expressions that will be executed during rendering, leading to Server-Side Template Injection (SSTI).

## Remediation

The fix removes the f-string interpolation that embedded user input directly into the Jinja2 template string. Instead, the template uses only Jinja2 variable placeholders ({{ title }}) and passes the user-controlled 'custom_header' value exclusively as a context variable to the render() method. This ensures user input is treated as data, not as part of the template syntax, preventing SSTI.

## Secure Code
```python
from flask import Flask, request
from jinja2 import Template
from markupsafe import escape
import boto3

app = Flask(__name__)
s3_client = boto3.client('s3')

@app.route('/cloud/report/generate', methods=['POST'])
def generate_cloud_report():
    bucket_name = request.form.get('bucket')
    report_format = request.form.get('format_template', 'default')
    resource_count = s3_client.list_objects_v2(Bucket=bucket_name).get('KeyCount', 0)
    report_template = request.form.get('custom_header', 'AWS S3 Bucket Analysis')
    template_str = "<html><head><title>{{ title }}</title></head><body><h1>{{ title }}</h1><p>Resources: {{ count }}</p></body></html>"
    jinja_template = Template(template_str)
    rendered_output = jinja_template.render(title=report_template, count=resource_count)
    return rendered_output, 200, {'Content-Type': 'text/html'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)
```
