flawopen.com/SSTI/Jinja2
Jinja2 的 render_template_string() 安全吗?
只有当模板源码由应用代码固定控制时,render_template_string() 才适合这样使用。如果在 Jinja 解析前把不可信输入拼入模板源码,就会产生服务器端模板注入(SSTI)。在 Flask 默认的非沙箱 Jinja 环境中,影响可能包括代码执行,具体取决于可访问对象、配置和应用本身;并非所有部署都会自动导致代码执行。
# All four are the same bug render_template_string(f"Hi {name}") render_template_string("Hi " + name) render_template_string("Hi %s" % name) render_template_string("Hi {}".format(name)) # A very common real-world shape: @app.errorhandler(404) def not_found(e): return render_template_string( f"<h1>No such page: " f"{request.path}</h1>" ), 404 # The URL path is attacker-controlled.
# Template is a literal; the user value # arrives as a named context variable render_template_string( "Hi {{ name }}", name=name ) # Better still, a real template file render_template("greeting.html", name=name) # For the 404 case, no template # evaluation is needed at all: return render_template("404.html", path=request.path), 404
攻击者控制模板源码为何会带来风险
Flask 默认会向模板提供 config、request 和 session 等应用全局对象。若攻击者控制模板源码,Jinja 会使用该环境可访问的对象求值模板表达式。实际影响取决于应用暴露的对象和能力。无害的算术表达式可以确认模板被求值,但它本身不能证明代码执行,也不能证明哪些数据可访问。
如果应用把敏感值放入模板上下文,模板可能会暴露这些值。例如 Flask 的 config 可能包含 SECRET_KEY 或数据库设置,但不同部署并不相同。不要把秘密放入模板可见的数据中,也不要把输出转义当作阻止模板求值的边界。
CVE-2024-56201 是另一种独立情况
这是 Jinja 编译器的另一项漏洞,并非普通 SSTI 的必要条件:当攻击者同时控制模板内容和模板文件名时,Jinja 3.0.0 至 3.1.4 受 CVE-2024-56201 影响。在该条件下,即使启用了沙箱,也可能执行任意 Python 代码。Jinja 3.1.5 修复了此问题。如果应用接受攻击者提供的模板或文件名,请查阅公告并升级。
常见出现位置
这种模式可能出现在回显请求路径的错误页面、邮件模板管理功能、租户自定义外观功能,以及本不应公开的预览或调试端点。
常见问题
不能。自动转义会转义渲染后的值,以降低输出中的 XSS 风险;它不会阻止把用户字符串编译为模板源码。这两种控制针对不同处理阶段。
Jinja 提供 SandboxedEnvironment 来渲染用户模板,但文档说明沙箱并非绝对安全。只传入必要数据,避免传入带副作用的对象,并在模板引擎之外限制 CPU、内存和进程资源。沙箱不能让解析前拼接不可信输入的做法变安全。
搜索 render_template_string,检查第一个参数是否为应用控制的固定字符串。将用户值作为具名上下文变量传入,或使用模板文件。还要检查动态编译模板的 API,以及攻击者能否控制模板名称。