首页/安全/漏洞与攻击面速查/模板注入(SSTI)原理与防护
Web 漏洞原理与防护55 条

模板注入(SSTI)原理与防护

服务端模板引擎把用户输入当模板内容渲染(而非作为变量数据)时,可能执行模板表达式能力范围内的逻辑;防护是区分『模板』与『数据』:不拼接模板字符串,只渲染固定模板并传参数。

漏洞与攻击面速查安全更新于 2026-10-13

模板注入怎么发生

SSTI 出现在「用户输入被当作模板内容渲染」的时候:服务端模板引擎把输入编译成模板,注入的表达式就按引擎能力被求值,可以读取变量、访问对象属性,严重时执行系统命令。根因是把数据和模板混在一起。正确做法是区分两者:模板内容只来自代码仓库,用户输入一律作为变量参数传入。

基本信息

常见引擎Jinja2(Python)、Twig(PHP)、Freemarker 与 Velocity(Java)、Thymeleaf(Java)
典型成因render_template_string(用户输入)、模板名或模板路径可控、模板字符串拼接
常见入口站内信与通知模板、报表导出、邮件模板、个性化页面、错误页
防护方向固定模板文件加变量参数,模板内容与请求数据彻底分离
加固手段沙箱环境、表达式能力白名单、模板来源限制
验证方式授权环境提交简单算术表达式,观察是否被求值

引擎与危险入口

引擎语言危险入口
Jinja2Pythonrender_template_string、模板名拼接
TwigPHP动态模板加载、未启用 sandbox 扩展
FreemarkerJava模板字符串拼接、new() 实例化能力
VelocityJava允许用户编辑模板内容
ThymeleafJava片段表达式、模板前缀可控
SmartyPHP模板内容可由用户提交

风险与安全对比(Jinja2)

风险与安全对比(Jinja2)
# 风险:把用户输入当模板内容渲染
# return render_template_string('Hello ' + name)
# 风险:模板名由请求决定
# return render_template(request.args['tpl'])

# 安全:模板始终来自代码仓库,用户输入只作为变量
return render_template('hello.html', name=name)
# 需要用户自定义展示时,用受限占位符替换,不暴露表达式能力

加固要点

加固要点
沙箱环境:Jinja2 用 SandboxedEnvironment,Twig 启用 sandbox 扩展
能力收敛:禁用危险过滤器与全局函数,只放行白名单
模板来源:仅允许仓库内固定文件,拒绝请求传入路径或模板名
审计:全量检索 render_template_string 与动态模板加载调用点

典型使用场景

  • 站内信、通知中心支持模板配置时的安全评审
  • 报表导出功能支持自定义表头与计算列
  • 运营后台可编辑邮件模板,需要确认渲染边界
  • 渗透测试报告中 SSTI 类问题的整改与复核
  • 老系统排查模板字符串拼接的历史写法
  • 引擎与沙箱版本升级后关注已知逃逸手段
需要用户自定义展示内容时,使用受限占位符替换(如只允许姓名、单号等白名单字段),而不是把模板引擎能力直接暴露给用户。
沙箱不是万能解,多种模板引擎历史上都出现过沙箱逃逸。最终还是要保证用户输入不进入模板层,沙箱只作为纵深防护。

易错点

  • 把沙箱模式当万能解:不同引擎沙箱存在逃逸历史,源头仍是不要让输入进入模板层
  • 只排查页面渲染,忽略站内信、报表导出、邮件模板等后台可编辑入口
  • 用黑名单过滤大括号当作防护,可用其他语法与编码变形绕过
  • 模板名由请求参数决定,等于把模板路径选择权交给攻击者
  • 沙箱与引擎版本长期不升级,已知逃逸手段持续可用

数据来源与参考

  • OWASP SSTI 说明
  • Jinja2 文档(SandboxedEnvironment)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌