{"title":"Jinja2 SSTI via Untrusted Template Rendering","language":"Python","severity":"Critical","cwe":"CWE-94","source_lines":[9],"flow_lines":[9,10,11,12],"sink_lines":[12],"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/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    resource_count = s3_client.list_objects_v2(Bucket=bucket_name).get('KeyCount', 0)\n    report_template = request.form.get('custom_header', 'AWS S3 Bucket Analysis')\n    template_str = f\"<html><head><title>{report_template}</title></head><body><h1>{{{{ title }}}}</h1><p>Resources: {{{{ count }}}}</p></body></html>\"\n    jinja_template = Template(template_str)\n    rendered_output = jinja_template.render(title=report_template, count=resource_count)\n    return rendered_output, 200, {'Content-Type': 'text/html'}\n\nif __name__ == '__main__':\n    app.run(host='0.0.0.0', port=8080)","explanation":"The application takes user-controlled input from 'custom_header' parameter and directly embeds it into a Jinja2 template string using an f-string. This user input becomes part of the template itself before being rendered, allowing attackers to inject malicious Jinja2 template expressions that will be executed during rendering, leading to Server-Side Template Injection (SSTI).","remediation":"The fix removes the f-string interpolation that embedded user input directly into the Jinja2 template string. Instead, the template uses only Jinja2 variable placeholders ({{ title }}) and passes the user-controlled 'custom_header' value exclusively as a context variable to the render() method. This ensures user input is treated as data, not as part of the template syntax, preventing SSTI.","secure_code":"from flask import Flask, request\nfrom jinja2 import Template\nfrom markupsafe import escape\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    resource_count = s3_client.list_objects_v2(Bucket=bucket_name).get('KeyCount', 0)\n    report_template = request.form.get('custom_header', 'AWS S3 Bucket Analysis')\n    template_str = \"<html><head><title>{{ title }}</title></head><body><h1>{{ title }}</h1><p>Resources: {{ count }}</p></body></html>\"\n    jinja_template = Template(template_str)\n    rendered_output = jinja_template.render(title=report_template, count=resource_count)\n    return rendered_output, 200, {'Content-Type': 'text/html'}\n\nif __name__ == '__main__':\n    app.run(host='0.0.0.0', port=8080)"}