# Jinja2 Server-Side Template Injection via Untrusted Template Variables

Language: Python
Severity: Critical
CWE: CWE-1336

## Source
9

## Flow
9-10-11

## Sink
11

## 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/bucket-report')
def generate_bucket_report():
    bucket_name = request.args.get('bucket', 'default-bucket')
    report_format = request.args.get('format', '{{bucket}}')
    metadata = s3_client.head_bucket(Bucket=bucket_name)
    template_str = f"S3 Bucket Analysis: {report_format}"
    rendered = Template(template_str).render(bucket=bucket_name, region=metadata['ResponseMetadata']['HTTPHeaders'].get('x-amz-bucket-region', 'us-east-1'))
    return rendered, 200
```

## Explanation

The application takes user-controlled input from the 'format' query parameter and embeds it directly into a Jinja2 template string without sanitization. This allows attackers to inject arbitrary Jinja2 template expressions that execute on the server, potentially leading to remote code execution through template sandbox escapes.

## Remediation

The fix eliminates Jinja2 template rendering of user-controlled input entirely. Instead, it uses Python's built-in str.format() with a whitelist of allowed placeholder values, and validates the format string against a strict regex pattern while explicitly rejecting any Jinja2 template syntax ({{ }}, {% %}, {# #}) to prevent server-side template injection.

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

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

ALLOWED_PLACEHOLDERS = {'bucket', 'region'}
PLACEHOLDER_PATTERN = re.compile(r'^[a-zA-Z0-9_ {},.:\-]+$')

@app.route('/cloud/bucket-report')
def generate_bucket_report():
    bucket_name = request.args.get('bucket', 'default-bucket')
    report_format = request.args.get('format', '{bucket}')
    
    # Validate that the format string only contains safe characters and simple placeholders
    if not PLACEHOLDER_PATTERN.match(report_format):
        return 'Invalid format string. Only alphanumeric characters and simple placeholders are allowed.', 400
    
    # Reject any Jinja2 template syntax ({{ }}, {% %}, {# #})
    if '{{' in report_format or '{%' in report_format or '{#' in report_format:
        return 'Template expressions are not allowed in format string.', 400
    
    metadata = s3_client.head_bucket(Bucket=bucket_name)
    region = metadata['ResponseMetadata']['HTTPHeaders'].get('x-amz-bucket-region', 'us-east-1')
    
    # Use Python's safe string formatting instead of Jinja2 template rendering
    try:
        safe_values = {
            'bucket': bucket_name,
            'region': region
        }
        rendered = 'S3 Bucket Analysis: ' + report_format.format(**safe_values)
    except (KeyError, IndexError, ValueError):
        return 'Invalid format string. Allowed placeholders: {bucket}, {region}', 400
    
    return rendered, 200
```
