{"title":"Jinja2 Template Injection via render_template_string()","language":"Python","severity":"Critical","cwe":"CWE-94","source_lines":[10],"flow_lines":[10,13],"sink_lines":[13],"vulnerable_code":"from flask import Flask, request, render_template_string\nimport boto3\n\napp = Flask(__name__)\ns3_client = boto3.client('s3')\n\n@app.route('/cloud/bucket-report')\ndef generate_s3_report():\n    bucket_name = request.args.get('bucket', 'default-bucket')\n    report_format = request.args.get('format', 'Bucket: {{bucket}}')\n    objects = s3_client.list_objects_v2(Bucket=bucket_name, MaxKeys=5)\n    object_count = objects.get('KeyCount', 0)\n    report_html = render_template_string(report_format, bucket=bucket_name, count=object_count)\n    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":"from flask import Flask, request, render_template_string\nimport boto3\nfrom markupsafe import escape\n\napp = Flask(__name__)\ns3_client = boto3.client('s3')\n\nALLOWED_FORMATS = {\n    'default': 'Bucket: {{bucket}} | Object Count: {{count}}',\n    'simple': 'Report for {{bucket}}: {{count}} objects',\n    'detailed': '<h2>S3 Bucket Report</h2><p>Bucket: {{bucket}}</p><p>Objects: {{count}}</p>'\n}\n\n@app.route('/cloud/bucket-report')\ndef generate_s3_report():\n    bucket_name = request.args.get('bucket', 'default-bucket')\n    format_key = request.args.get('format', 'default')\n    \n    if format_key not in ALLOWED_FORMATS:\n        format_key = 'default'\n    \n    report_format = ALLOWED_FORMATS[format_key]\n    \n    objects = s3_client.list_objects_v2(Bucket=bucket_name, MaxKeys=5)\n    object_count = objects.get('KeyCount', 0)\n    \n    report_html = render_template_string(\n        report_format,\n        bucket=escape(bucket_name),\n        count=object_count\n    )\n    return report_html"}