flawopen.com/Vulnerabilities/Server-Side Template Injection (SSTI)
Server-Side Template Injection (SSTI):成因与修复
Impact depends on reachable template behavior
CWE-1336
Template Engine Security
💡 通俗解释:数据还是打印机指令
想象一家邮购公司使用“您好,{{ name }}”这样的信件模板。安全做法是把固定模板和客户姓名作为数据分别交给打印机。不安全做法则是在打印前把客户文本插入模板指令中。如果模板引擎把这些文本解释为模板语法,就会发生 Server-Side Template Injection。后续影响取决于引擎、配置、暴露的对象和进程权限;表达式求值本身并不意味着可以执行操作系统命令。
目标: Jinja、Twig、FreeMarker、ERB 等引擎中的模板源码构造与服务端渲染。
脆弱边界: 不可信输入被编译或解析为模板源码,而不是作为数据值传递给可信模板。
影响: 模板表达式求值;进一步后果取决于引擎能力、暴露对象、沙箱和进程权限。
修复: 保持模板源码可信且静态;只将不可信输入作为上下文数据传递。确实需要用户编写模板时,应隔离渲染过程。
核心概念与子系统术语
- 模板引擎
- Jinja 或 Twig 等服务端组件,负责解析模板语法,并结合模板和上下文数据生成输出。
- 模板源码注入
- 外部文本在解析前被加入模板源码,导致引擎把它当作模板语法而非普通数据解释。
- 上下文数据
- 单独传递给固定可信模板的值。正确分离可防止
{{ 7 * 7 }}这样的值被重新解析为模板源码。 - CWE-1336
- 模板引擎中特殊元素未被正确中和。该弱点描述外部输入被不安全地作为模板语法处理。
分步骤攻击流程
Step 1
攻击者可控输入到达模板处理路径
表单字段、已保存的个人资料值或其他受外部影响的字符串到达创建或编译模板的代码。漏洞边界是构造源码,而非单纯渲染一个值。
Step 2
应用混合数据与模板指令
应用把不可信字符串插入模板源码(例如将 f-string 传给 render_template_string),而不是使用静态模板并单独传递上下文值。
Step 3
引擎解析组合后的源码
引擎根据自身语言和配置解释表达式。无害的算术表达式可以证明发生了求值,但这本身不能证明可以访问文件、秘密或系统命令。
Step 4
影响取决于引擎能力和部署方式
后续影响取决于可访问的对象和函数、沙箱行为、引擎限制及 Web 进程权限。在某些配置下可能导致 RCE,但这不是每个 SSTI 的必然结果。
机制与根本原因
SSTI 发生在应用让外部影响的文本在解析前成为模板程序的一部分时。在 Flask/Jinja 中,易受攻击的模式是用请求数据构造新的源码字符串并传给 render_template_string();HTML 转义保护的是输出上下文,不能把不可信模板源码变成数据。修复方法是加载固定模板,并将值作为独立上下文传递。如果必须支持用户编写模板,应只暴露最小数据模型,并在具备资源限制的隔离 worker 中渲染。沙箱是纵深防御,不是暴露秘密或强大应用对象的理由。
flask_app.py(不安全模式)易受攻击
# VULNERABLE: request data becomes template source before parsing.
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route("/greet")
def greet():
name = request.args.get("name", "Guest")
# A value such as {{ 7 * 7 }} is interpreted by the template engine.
source = f"<h1>Welcome, {name}!</h1>"
return render_template_string(source)
flask_app.py(安全修复)安全
# HARDENED: fixed template source; request data stays a context value.
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route("/greet")
def greet():
name = request.args.get("name", "Guest")
return render_template("greet.html", name=name)
# templates/greet.html is trusted, static source:
# <h1>Welcome, {{ name }}!</h1>
攻击序列与概念示例
- 1来源: 受外部影响的值到达模板构造路径。
- 2不安全组合: 应用将该值放入模板源码,而非作为独立上下文数据传递。
- 3解释执行: 引擎解析组合后的源码。在授权测试环境中,无害算术表达式可确认发生了求值。
- 4条件性影响: 后续访问取决于引擎、暴露对象、配置和进程权限;不能仅凭表达式求值就断定存在 RCE。
工程防护与预防规则
- ✓保持源码固定: 从文件或受控 loader 加载可信模板;用户输入只作为上下文值传递。不要把请求数据拼接进源码。
- ✓区分 SSTI 与 HTML 注入: HTML 转义保护输出上下文,不能修复不可信输入被解析为模板源码的数据流。
- ✓限制用户编写的模板: 若该功能确有必要,按引擎建议使用沙箱,只暴露最小数据模型,并在资源受限的隔离 worker 中渲染。遵循引擎安全说明。
- ✓限制影响范围: 以非 root 服务运行渲染器,并限制其文件、网络和秘密访问。这是遏制措施,不是主要修复。
- ✓添加回归测试: 将
{{ 7 * 7 }}这样的无害标记作为数据传入,并确认其仍是数据;同时验证模板只来自可信 loader。
主要来源与相关安全主题:完整安全目录 →