# Jinja2 Server-Side Template Injection via Untrusted Template Rendering

Language: Python
Severity: Critical
CWE: CWE-1336

## Source
16

## Flow
15-16-17

## Sink
17

## Vulnerable Code
```python
from flask import Flask, request
from jinja2 import Environment
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')
    metrics = s3_client.get_bucket_metrics(Bucket=bucket_name)
    
    jinja_env = Environment()
    user_template = request.form.get('custom_header', '<h1>AWS S3 Report</h1>')
    compiled = jinja_env.from_string(user_template)
    header_html = compiled.render(bucket=bucket_name, data=metrics)
    
    full_report = f"{header_html}<div>Storage: {metrics.get('size')} GB</div>"
    return full_report, 200
```

## Explanation

The application takes user-controlled input from 'custom_header' form parameter and passes it directly to Jinja2's from_string() method without sanitization. This allows attackers to inject arbitrary Jinja2 template expressions that execute on the server during render(), leading to Server-Side Template Injection (SSTI) which can result in remote code execution.

## Remediation

The fix replaces the unrestricted Jinja2 Environment with SandboxedEnvironment, which prevents access to dangerous attributes and methods during template rendering. Additionally, a sanitization function blocks known dangerous patterns (like '__', 'config', 'import', etc.) before the template is compiled, and template context values are HTML-escaped to prevent XSS.

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

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

ALLOWED_PLACEHOLDERS = {'bucket_name', 'report_date'}
SAFE_TEMPLATE_PATTERN = re.compile(r'^[\w\s<>/="\-{}.,:#;!()]+$')

def sanitize_template(template_str):
    """Validate that the template only contains safe HTML and whitelisted placeholders."""
    if not template_str or len(template_str) > 500:
        return '<h1>AWS S3 Report</h1>'
    
    # Block dangerous Jinja2 constructs
    dangerous_patterns = [
        '__', 'config', 'class', 'mro', 'subclasses', 'globals',
        'import', 'popen', 'eval', 'exec', 'getattr', 'setattr',
        'builtins', 'lipsum', 'cycler', 'joiner', 'namespace',
        'request', 'self', '[]', '|attr'
    ]
    template_lower = template_str.lower()
    for pattern in dangerous_patterns:
        if pattern in template_lower:
            return '<h1>AWS S3 Report</h1>'
    
    return template_str

@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')
    metrics = s3_client.get_bucket_metrics(Bucket=bucket_name)
    
    # Use SandboxedEnvironment to prevent dangerous operations
    jinja_env = SandboxedEnvironment()
    
    user_template = request.form.get('custom_header', '<h1>AWS S3 Report</h1>')
    
    # Sanitize the user-provided template
    safe_template = sanitize_template(user_template)
    
    compiled = jinja_env.from_string(safe_template)
    # Only pass safe, pre-escaped values to the template context
    header_html = compiled.render(
        bucket=escape(bucket_name),
        data={'size': metrics.get('size')}
    )
    
    full_report = f"{header_html}<div>Storage: {escape(str(metrics.get('size')))} GB</div>"
    return full_report, 200
```
