{"title":"Jinja2 Server-Side Template Injection via Untrusted Template Rendering","language":"Python","severity":"Critical","cwe":"CWE-1336","source_lines":[16],"flow_lines":[15,16,17],"sink_lines":[17],"vulnerable_code":"from flask import Flask, request\nfrom jinja2 import Environment\nimport boto3\n\napp = Flask(__name__)\ns3_client = boto3.client('s3')\n\n@app.route('/cloud/report/generate', methods=['POST'])\ndef generate_cloud_report():\n    bucket_name = request.form.get('bucket')\n    report_format = request.form.get('format_template', 'default')\n    metrics = s3_client.get_bucket_metrics(Bucket=bucket_name)\n    \n    jinja_env = Environment()\n    user_template = request.form.get('custom_header', '<h1>AWS S3 Report</h1>')\n    compiled = jinja_env.from_string(user_template)\n    header_html = compiled.render(bucket=bucket_name, data=metrics)\n    \n    full_report = f\"{header_html}<div>Storage: {metrics.get('size')} GB</div>\"\n    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":"from flask import Flask, request\nfrom jinja2 import Environment, SandboxedEnvironment\nfrom markupsafe import escape\nimport boto3\nimport re\n\napp = Flask(__name__)\ns3_client = boto3.client('s3')\n\nALLOWED_PLACEHOLDERS = {'bucket_name', 'report_date'}\nSAFE_TEMPLATE_PATTERN = re.compile(r'^[\\w\\s<>/=\"\\-{}.,:#;!()]+$')\n\ndef sanitize_template(template_str):\n    \"\"\"Validate that the template only contains safe HTML and whitelisted placeholders.\"\"\"\n    if not template_str or len(template_str) > 500:\n        return '<h1>AWS S3 Report</h1>'\n    \n    # Block dangerous Jinja2 constructs\n    dangerous_patterns = [\n        '__', 'config', 'class', 'mro', 'subclasses', 'globals',\n        'import', 'popen', 'eval', 'exec', 'getattr', 'setattr',\n        'builtins', 'lipsum', 'cycler', 'joiner', 'namespace',\n        'request', 'self', '[]', '|attr'\n    ]\n    template_lower = template_str.lower()\n    for pattern in dangerous_patterns:\n        if pattern in template_lower:\n            return '<h1>AWS S3 Report</h1>'\n    \n    return template_str\n\n@app.route('/cloud/report/generate', methods=['POST'])\ndef generate_cloud_report():\n    bucket_name = request.form.get('bucket')\n    report_format = request.form.get('format_template', 'default')\n    metrics = s3_client.get_bucket_metrics(Bucket=bucket_name)\n    \n    # Use SandboxedEnvironment to prevent dangerous operations\n    jinja_env = SandboxedEnvironment()\n    \n    user_template = request.form.get('custom_header', '<h1>AWS S3 Report</h1>')\n    \n    # Sanitize the user-provided template\n    safe_template = sanitize_template(user_template)\n    \n    compiled = jinja_env.from_string(safe_template)\n    # Only pass safe, pre-escaped values to the template context\n    header_html = compiled.render(\n        bucket=escape(bucket_name),\n        data={'size': metrics.get('size')}\n    )\n    \n    full_report = f\"{header_html}<div>Storage: {escape(str(metrics.get('size')))} GB</div>\"\n    return full_report, 200"}