thymeleaf模板注入学习与研究--查找与防御
字数 918 2025-08-12 11:34:43
Thymeleaf模板注入漏洞研究与防御指南
一、漏洞概述
Thymeleaf模板注入是一种服务器端模板注入(SSTI)漏洞,当攻击者能够控制模板名称或部分模板内容时,可以执行任意Java代码,导致远程代码执行(RCE)。
二、常见漏洞场景
2.1 模板参数外部可控
@RequestMapping("/path")
public String path(@RequestParam String lang) {
return lang;
}
漏洞分析:
- 直接返回用户输入的参数作为模板名称
- 常见于主题切换、语言切换等功能
2.2 使用@GetMapping注解且无返回值
@GetMapping("/doc/{document}")
public void getDocument(@PathVariable String document) {
logger.info("Retrieving " + document);
}
漏洞分析:
- 根据Spring Boot定义,无返回值时以路由路径作为视图名称
- 请求URL会被直接作为模板名称解析
三、Payload构造方法
3.1 基本Payload结构
__${恶意表达式}__::.x
3.2 实际攻击示例
URL编码后的Payload:
__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22calc%22).getInputStream()).next()%7d__::.x
对应原始表达式:
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("calc").getInputStream()).next()}__::.x
3.3 不同场景下的Payload变体
-
路径拼接场景:
- 必须以
::.x结尾 - 示例请求:
/doc/__${payload}__::.x
- 必须以
-
片段表达式场景:
- 可以省略
::.x - 示例请求:
/fragment?section=__${payload}__
- 可以省略
四、漏洞检测方法
4.1 黑盒测试
- 寻找主题切换、背景更改等功能点
- 将参数替换为测试Payload
- 观察系统响应或执行结果
4.2 白盒审计
4.2.1 模板参数外部可控审计
- 收集所有模板文件名称
- 搜索控制器返回语句:
return.*?\".*?模板名称 - 检查:
- 返回参数是否外部可控
- 是否不含HttpServlet相关对象
- 是否没有重定向(redirect)
4.2.2 无返回值GetMapping审计
使用正则搜索:
@GetMapping$.*?$\s*public\s+void
五、漏洞修复方案
5.1 推荐修复方案(白名单方式)
@RequestMapping("/safepath")
public String path(@RequestParam int lang) {
int num = request.getParameter("lang");
HashMap<Integer, String> tems = new HashMap<Integer, String>();
tems.put(1, "red template");
tems.put(2, "yellow template");
tems.put(3, "green template");
return tems.get(num);
}
5.2 其他修复方案
-
使用@ResponseBody或@RestController:
@RestController public class SafeController { // 不再调用模板解析 } -
使用redirect或forward前缀:
@GetMapping("/safe/redirect") public String redirect(@RequestParam String url) { return "redirect:" + url; } -
添加HttpServletResponse参数:
@GetMapping("/safe/doc/{document}") public void getDocument(@PathVariable String document, HttpServletResponse response) { log.info("Retrieving " + document); }
六、防御要点总结
- 避免直接使用用户输入作为模板名称
- 对必须使用外部输入的模板名称实施白名单验证
- 在不需要模板渲染的场景使用@ResponseBody
- 对高风险功能进行代码审查
- 保持Thymeleaf和Spring Boot版本更新
七、参考代码示例
7.1 漏洞代码示例
@Controller
public class VulnerableController {
// 场景1:直接返回用户输入
@RequestMapping("/vuln1")
public String vuln1(@RequestParam String template) {
return template;
}
// 场景2:无返回值GetMapping
@GetMapping("/vuln2/{param}")
public void vuln2(@PathVariable String param) {
logger.info("Parameter: " + param);
}
// 场景3:片段表达式注入
@GetMapping("/vuln3")
public String vuln3(@RequestParam String section) {
return "welcome :: " + section;
}
}
7.2 安全代码示例
@Controller
public class SafeController {
// 安全方式1:白名单
@RequestMapping("/safe1")
public String safe1(@RequestParam int templateId) {
Map<Integer, String> templates = Map.of(
1, "template1",
2, "template2"
);
return templates.getOrDefault(templateId, "default");
}
// 安全方式2:使用@ResponseBody
@GetMapping("/safe2")
@ResponseBody
public String safe2() {
return "This will not be processed as template";
}
// 安全方式3:强制重定向
@GetMapping("/safe3")
public String safe3() {
return "redirect:/safe-page";
}
}