参考文章:

若依最新版本4.8.1漏洞 SSTI绕过获取ShiroKey至RCE(全JAVA版本绕过,附带POC)

https://mp.weixin.qq.com/s/4yi0UOTgBCsGK6J8qSz8tQ

某依最新版本稳定4.8.1 RCE (Thymeleaf模板注入绕过)

https://mp.weixin.qq.com/s/uxvGbO4biM87DVSXA_ZlQw

环境搭建

源码:

https://github.com/yangzongzhuan/RuoYi/releases

参考文档:RuoYi-4.8.1\doc\若依环境使用手册.docx

导入数据库

修改数据库配置:RuoYi-4.8.1\ruoyi-admin\src\main\resources\application-druid.yml

服务器端口:RuoYi-4.8.1\ruoyi-admin\src\main\resources\application.yml

启动若依

默认密码为 admin/admin123

漏洞复现

POC:

shiro 利用

漏洞分析

漏洞点

RuoYi-4.8.1\ruoyi-admin\src\main\java\com\ruoyi\web\controller\monitor\CacheController.java

用户在 http://localhost:80/monitor/cache/getNames 接口处 POST 传入的 fragment 参数,被直接拼接到 Thymeleaf 模板路径中,return返回结果使用 Thymeleaf 片段语法(::)拼接

Thymeleaf片段语法格式为:前缀 + "/cache::" + 片段名

攻击者通过 fragment 参数注入 SpEL 表达式,Thymeleaf 在解析视图名时会自动执行

通过__|$${表达式}|__::.x 该格式可进行绕过,先来试验一下:

关于 payload 的构造可以看 https://mp.weixin.qq.com/s/4yi0UOTgBCsGK6J8qSz8tQ 这篇文章,讲解十分详细

最新版本中禁用的语法:

  • ❌ T(java.lang.Runtime)(类型引用)
  • ❌ new java.io.File()(对象实例化)
  • ❌ T ()(带空格的类型引用)
  • ❌ param(参数引用)

通过链式调用是可以成功执行的,因此表达式格式可以使用方法链式调用。之前的利用链不可用,经过查找,发现可以在SecurityManager 中获取 getRuntime 方法,根据之前payload的调用方法,构造出以下利用链:

获取SecurityManager → 获取Class → 获取ClassLoader → 加载Runtime类 → 获取getRuntime方法 → 获取Runtime实例 → 获取exec方法 → 执行系统命令

那么构造出如下利用链:

直接500报错,那么为什么这个链看起来是正常的却不能执行?


先一步一步来,一点一点删除调用的方法,直到删除到loadClass('java.lang.Runtime') 这个地方成功输出。

看来是有某个安全监测机制不让获取getRuntime方法,那么还有机会能绕过吗?有的。在 SpEL 中,对于无参数的方法调用,括号 () 是可选的,getMethods()和getMethods作用是一样的,那么试一下把括号去掉是否报错。没有报错,成功获取到getRuntime方法

那现在继续调试上面完整的利用链。现在只获取到了方法,但是没反射调用,

由于获取到的是第一个exec方法,在查找当前exec方法的用法后,构造以下利用链:

等价于Runtime.exec(Runtime.getRuntime,"calc");,意思就是执行 Runtime.getRuntime 中的 exec方法

Payload 解析

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

拼接后完整视图名变为:monitor/cache/cache::__|${...SpEL...}|__::.x

第一层:__|${...SpEL表达式...}|__::.x

Thymeleaf 预处理标记,__...__ 之间的内容会在视图解析之前先被预处理执行

|...| 是 Thymeleaf 的字面量替换语法,${...} 内部为 SpEL 表达式

伪造一个不存在的片段名 .x,使其符合 模板::片段 的语法格式,否则解析会报错

第二层:#response.getWriter().print(...)

#response Thymeleaf 上下文中内置的 HttpServletResponse 对象

.getWriter() 获取响应的 PrintWriter,可以直接向 HTTP 响应体写内容

.print(...) 将结果直接写入 HTTP 响应,攻击者在响应中就能看到输出

**第三层: **@securityManager.rememberMeManager.cipherKey

@securityManager SpEL 的 @beanName 语法,从 Spring 容器中按名称获取 Bean。RuoYi 使用 Shiro,会注册一个名为 securityManager 的 Bean

.rememberMeManager 获取 Shiro SecurityManager 中的 RememberMe 管理器(CookieRememberMeManager)

.cipherKey 获取 RememberMe 功能使用的 AES 加密密钥(byte[] 类型)

第四层:

因为 cipherKey 是 byte[],直接 print 会乱码,所以需要 Base64 编码:

漏洞链:泄露 Shiro Key → 反序列化 RCE

泄露的密钥是 Shiro RememberMe Cookie 的 AES 加密密钥。攻击者拿到此密钥后:

使用 ysoserial 等工具生成恶意 Java 反序列化 payload

用泄露的 AES Key 加密该 payload

作为 rememberMe Cookie 发送

Shiro 解密 → 反序列化 → 远程代码执行(RCE)