# Jinja2 Template Injection via render_template_string()

Language: Python
Severity: Critical
CWE: CWE-94

## Source
10

## Flow
10-13

## Sink
13

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

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

@app.route('/cloud/bucket-report')
def generate_s3_report():
    bucket_name = request.args.get('bucket', 'default-bucket')
    report_format = request.args.get('format', 'Bucket: {{bucket}}')
    objects = s3_client.list_objects_v2(Bucket=bucket_name, MaxKeys=5)
    object_count = objects.get('KeyCount', 0)
    report_html = render_template_string(report_format, bucket=bucket_name, count=object_count)
    return report_html
```

## Explanation

The application accepts user-controlled input from the 'format' query parameter and passes it directly to render_template_string() without sanitization. This allows attackers to inject arbitrary Jinja2 template expressions that execute on the server, leading to remote code execution through Server-Side Template Injection (SSTI).

## Remediation

The fix eliminates template injection by replacing the user-supplied format string with a whitelist of predefined safe templates. Users now select a format by key name (e.g., 'default', 'simple', 'detailed') rather than providing arbitrary template strings. Additionally, user-supplied values like bucket_name are escaped before being passed to the template to prevent XSS.

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

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

ALLOWED_FORMATS = {
    'default': 'Bucket: {{bucket}} | Object Count: {{count}}',
    'simple': 'Report for {{bucket}}: {{count}} objects',
    'detailed': '<h2>S3 Bucket Report</h2><p>Bucket: {{bucket}}</p><p>Objects: {{count}}</p>'
}

@app.route('/cloud/bucket-report')
def generate_s3_report():
    bucket_name = request.args.get('bucket', 'default-bucket')
    format_key = request.args.get('format', 'default')
    
    if format_key not in ALLOWED_FORMATS:
        format_key = 'default'
    
    report_format = ALLOWED_FORMATS[format_key]
    
    objects = s3_client.list_objects_v2(Bucket=bucket_name, MaxKeys=5)
    object_count = objects.get('KeyCount', 0)
    
    report_html = render_template_string(
        report_format,
        bucket=escape(bucket_name),
        count=object_count
    )
    return report_html
```
