FreeMarker入门到简要分析模版注入
字数 1626 2025-08-24 16:48:15
FreeMarker模板引擎从入门到安全分析
一、FreeMarker简介
FreeMarker是一款模板引擎,基于模板和要改变的数据生成输出文本(HTML网页、电子邮件、配置文件、源代码等)。它不是面向最终用户,而是一个Java类库,是程序员可以嵌入到开发产品中的组件。
主要特点:
- 使用专用语言FreeMarker Template Language (FTL)
- 专注于数据展示,业务逻辑在模板外处理
- 语法类似JSP的EL表达式或Thymeleaf,使用
${}或标签
二、环境搭建与基本配置
1. SpringBoot整合FreeMarker
依赖引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
配置文件示例:
server:
port: 8081
spring:
freemarker:
# 配置项说明
allow-request-override: false # HttpServletRequest属性是否可覆盖model同名项
request-context-attribute: req # req访问request
suffix: .html # 模板后缀,默认为.ftl
content-type: text/html;charset=utf-8 # 响应内容类型
enabled: true # 是否允许mvc使用freemarker
cache: false # 是否开启模板缓存
template-loader-path: classpath:/templates/ # 模板加载路径
charset: UTF-8 # 模板编码
三、基本语法与使用
1. Controller示例
@GetMapping
public String index(Model model){
model.addAttribute("msg", "你好,老铁");
model.addAttribute("flag", true);
model.addAttribute("createData", new Date());
return "index";
}
2. 模板文件(index.html)示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>hello,家人们</h1>
<h1>msg:${msg}</h1>
<!-- 布尔类型转换 -->
<h1>f1:${flag?string}</h1>
${flag?string("yes","no")}
<!-- 日期类型处理 -->
${createData?date} <!-- 年月日 -->
${createData?time} <!-- 时分秒 -->
${createData?datetime} <!-- 年月日时分秒 -->
${createData?string("yyyy/MM/dd HH")} <!-- 自定义格式 -->
<!-- 字符串处理 -->
<h5>字符串类型</h5>
${msg}
${msg?substring(0,1)} <!-- 截取字符串 -->
${msg?uncap_first} <!-- 首字母小写 -->
${msg?cap_first} <!-- 首字母大写 -->
</body>
</html>
3. 常用语法说明
- 变量输出:
${variable} - 布尔值转换:
flag?string:转换为字符串flag?string("yes","no"):三元运算形式
- 日期处理:
?date:只显示年月日?time:只显示时分秒?datetime:显示完整日期时间?string("format"):自定义格式
- 字符串处理:
?substring(start,end):字符串截取?uncap_first:首字母小写?cap_first:首字母大写
四、安全漏洞分析
1. XSS漏洞
当模板中直接输出用户可控内容时,可能导致XSS漏洞:
model.addAttribute("msg", "<script>alert(1)</script>");
模板中直接使用${msg}输出时,会解析script标签,导致XSS。
2. RCE漏洞(远程代码执行)
攻击Payload:
<#assign value="freemarker.template.utility.Execute"?new()>${value("open -a Calculator")}
漏洞分析:
-
assign指令:
- 语法:
<#assign key=value> - 示例:
<#assign value="freemarker.template.utility.Execute"?new()> - 作用:创建一个名为value的变量,其值为
Execute类的新实例
- 语法:
-
?new():
- FreeMarker从2.3.17版本开始,只支持实现了
TemplateModel接口的类 Execute类实现了TemplateMethodModel接口(继承自TemplateModel)- 因此可以通过
?new()调用其构造函数
- FreeMarker从2.3.17版本开始,只支持实现了
-
方法调用:
${value("open -a Calculator")}- 调用
Execute实例的exec方法,传入命令参数 - 参数实际上以List形式传递,取第一个元素作为命令执行
底层机制:
- FreeMarker允许通过
?new()实例化实现了TemplateModel接口的类 freemarker.template.utility.Execute类提供了命令执行功能- 攻击者可以构造任意命令实现RCE
3. 漏洞验证示例
测试类:
package com.springboot.pojo;
public class Test {
public Test() {
System.out.println("123");
}
public void hello() {
System.out.println("Hello");
}
}
模板测试:
<#assign value="com.springboot.pojo.Test"?new()>
成功调用Test类的构造函数,输出"123"
五、安全防护建议
-
输入过滤:
- 对所有用户输入进行严格的过滤和转义
- 特别是对于要在模板中输出的内容
-
FreeMarker配置:
- 使用最新版本的FreeMarker
- 禁用危险的指令和功能
-
沙箱环境:
- 在可能的情况下,使用沙箱环境运行FreeMarker
- 限制可访问的Java类和方法的范围
-
代码审计:
- 检查所有使用
?new()的地方 - 确保不会动态加载不受信任的类
- 检查所有使用
-
输出编码:
- 对于所有动态内容输出,使用适当的编码函数
- 例如:
${userInput?html}进行HTML编码
六、深入分析资源
如需深入了解FreeMarker模板注入的详细流程,可参考:
https://blog.csdn.net/qq_38154820/article/details/127982704
该文章详细分析了FreeMarker模板注入的底层机制和调用流程,包括断点调试和参数传递过程。