{"title":"Jinja2 Server-Side Template Injection via Untrusted render_template_string()","language":"Python","severity":"Critical","cwe":"CWE-1336","source_lines":[10],"flow_lines":[10,12],"sink_lines":[12],"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_bucket_report():\n    bucket_name = request.args.get('bucket', 'default-bucket')\n    report_format = request.args.get('format', 'Bucket: {{bucket}}')\n    object_count = s3_client.list_objects_v2(Bucket=bucket_name).get('KeyCount', 0)\n    rendered_report = render_template_string(report_format, bucket=bucket_name, count=object_count)\n    return rendered_report, 200, {'Content-Type': 'text/plain'}","explanation":"The application accepts user-controlled input from the 'format' parameter (line 10) and passes it directly to render_template_string() (line 12) without validation or sanitization. This allows attackers to inject arbitrary Jinja2 template expressions that execute server-side, leading to Server-Side Template Injection (SSTI) which can result in remote code execution.","remediation":"The fix eliminates the SSTI vulnerability by removing render_template_string() entirely and replacing it with a whitelist of predefined safe format strings. Users now select from approved format keys instead of supplying arbitrary template strings, and Python's str.format() is used for safe string interpolation with HTML-escaped values.","secure_code":"from flask import Flask, request, escape\nimport boto3\n\napp = Flask(__name__)\ns3_client = boto3.client('s3')\n\nPREDEFINED_FORMATS = {\n    'simple': 'Bucket: {bucket}',\n    'detailed': 'Bucket: {bucket} | Object Count: {count}',\n    'summary': 'Report for \"{bucket}\": {count} objects found.'\n}\n\n@app.route('/cloud/bucket-report')\ndef generate_bucket_report():\n    bucket_name = request.args.get('bucket', 'default-bucket')\n    format_key = request.args.get('format', 'simple')\n    \n    if format_key not in PREDEFINED_FORMATS:\n        return 'Invalid format. Available formats: ' + ', '.join(PREDEFINED_FORMATS.keys()), 400, {'Content-Type': 'text/plain'}\n    \n    object_count = s3_client.list_objects_v2(Bucket=bucket_name).get('KeyCount', 0)\n    \n    report_template = PREDEFINED_FORMATS[format_key]\n    rendered_report = report_template.format(\n        bucket=escape(bucket_name),\n        count=object_count\n    )\n    return rendered_report, 200, {'Content-Type': 'text/plain'}"}