{"title":"Jinja2 Server-Side Template Injection via Untrusted Template Variables","language":"Python","severity":"Critical","cwe":"CWE-1336","source_lines":[9],"flow_lines":[9,10,11],"sink_lines":[11],"vulnerable_code":"from flask import Flask, request\nfrom jinja2 import Template\nimport boto3\n\napp = Flask(__name__)\ns3_client = boto3.client('s3')\n\n@app.route('/cloud/bucket-report')\ndef generate_bucket_report():\n    bucket_name = request.args.get('bucket', 'default-bucket')\n    report_format = request.args.get('format', '{{bucket}}')\n    metadata = s3_client.head_bucket(Bucket=bucket_name)\n    template_str = f\"S3 Bucket Analysis: {report_format}\"\n    rendered = Template(template_str).render(bucket=bucket_name, region=metadata['ResponseMetadata']['HTTPHeaders'].get('x-amz-bucket-region', 'us-east-1'))\n    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":"from flask import Flask, request, escape\nfrom jinja2 import Template, SandboxedEnvironment\nimport boto3\nimport re\n\napp = Flask(__name__)\ns3_client = boto3.client('s3')\n\nALLOWED_PLACEHOLDERS = {'bucket', 'region'}\nPLACEHOLDER_PATTERN = re.compile(r'^[a-zA-Z0-9_ {},.:\\-]+$')\n\n@app.route('/cloud/bucket-report')\ndef generate_bucket_report():\n    bucket_name = request.args.get('bucket', 'default-bucket')\n    report_format = request.args.get('format', '{bucket}')\n    \n    # Validate that the format string only contains safe characters and simple placeholders\n    if not PLACEHOLDER_PATTERN.match(report_format):\n        return 'Invalid format string. Only alphanumeric characters and simple placeholders are allowed.', 400\n    \n    # Reject any Jinja2 template syntax ({{ }}, {% %}, {# #})\n    if '{{' in report_format or '{%' in report_format or '{#' in report_format:\n        return 'Template expressions are not allowed in format string.', 400\n    \n    metadata = s3_client.head_bucket(Bucket=bucket_name)\n    region = metadata['ResponseMetadata']['HTTPHeaders'].get('x-amz-bucket-region', 'us-east-1')\n    \n    # Use Python's safe string formatting instead of Jinja2 template rendering\n    try:\n        safe_values = {\n            'bucket': bucket_name,\n            'region': region\n        }\n        rendered = 'S3 Bucket Analysis: ' + report_format.format(**safe_values)\n    except (KeyError, IndexError, ValueError):\n        return 'Invalid format string. Allowed placeholders: {bucket}, {region}', 400\n    \n    return rendered, 200"}