笑看天下无敌手 发表于 2023-4-11 17:14:29

若依 4.7.6 版本 任意文件下载漏洞(审计复现)

环境:
  JDK 8u202
  MySQL 5.5.29
  RuoYi 4.7.6
参考公众号(这不是我公众号啊)
https://mp.weixin.qq.com/s/IrqLp2Z3c941NiN0fFcDMA
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411094845833-354208388.png
这是一个最新版漏洞
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411095541895-1736284957.png
其中关键请求需要3个(新增计划任务、执行计划任务、下文文件),没有给出内部实现关键信息,尝试审计
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411095451269-1719284362.png
首先去官网下载最新版本
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411095611048-620332773.png
选择最新版本
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411095635200-301718141.png
解压并部署,只需配置好mysql数据库即可
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411095920703-63148017.png
导入sql数据
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411095836386-589042292.png
springboot启动!
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411095955455-2097817503.png
看一下登录密码,先去登录界面
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411100103226-2094020507.png
默认账户密码已经填写好了,输入验证码即可登录
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411100119657-1433059417.png
找到定时任务
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411100550753-1785182955.png
尝试一下默认计划任务,点击编辑,查看该功能的作用
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411100652860-1581131716.png
发现计划任务可以调用bean或以包全称调用方法
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411100735481-43061261.png
执行一次试试
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411100850071-1314758187.png
发现终端有输出
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411101012701-1459616895.png
找到调用 ryTask.ryParams('ry') 的 bean
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411101146041-1835063266.png
所以计划任务是可以直接调用bean并执行对应的方法的,那返回看公众号内发的请求,发现跟换指定的bean
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411101455735-144946800.png、
去查看一下指定 ruoYiConfig 的 bean 中 setProfile 方法的作用
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411101820469-1048733302.png
发现修改的是当前类属性
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411101842213-426924579.png
是一个上传路径
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411101913429-1162975424.png
那一般情况下,上传路径都是在配置文件中提前配置好的,找到配置文件,发现默认是 D 盘的某目录下
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411102044255-455538878.png
回到计划任务页面,添加一个新任务
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411102226228-1545791332.png
填写好对应参数,修改成我们要下载的文件地址,cron表示式按照原有的测试计划任务模板填写即可
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411102440254-97670685.png
提交,追踪请求,发现提交到 /monitor/job/add 路径下
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411102810975-2008034900.png
追踪请求链
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411103109759-1677902964.png
发现有很多过滤
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411103136357-1009293871.png
最重要的是最后的黑白名单过滤,打断点
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411103244155-1717816063.png
进入第一个黑名单判断
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411103323818-201925948.png
这里的 JOB_ERROR_STR 是黑名单列表
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411103424976-174847410.png
跟进到 containsAnyIgnoreCase 方法,发现只是对字符串进行黑名单判断,未作修改
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411103751128-118755905.png
继续跟进到下一个白名单判断
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411103840430-1387729290.png
方法里面首先将传入的值进行了拆分,拆分后的值准备通过 getBean 的方式转换为 bean
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411104141442-901521475.png
这里成获取到对象,将转换为bean的对象再进行白名单检测
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411104802786-1071453598.png
跟进 containsAuyIgnoreCase 方法,这里是将对象包名传了进来,用来判断调用的是不是顶级包下的对象
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411105022428-2023447774.png
这里成功绕过,返回 true 绕过判断,进入到 service 层
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411105153306-334133864.png
发现只是将请求保存到了数据库
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411105335809-472799426.png
回到 计划任务页面,发现新增 Test 计划任务 成功
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411105502057-351903927.png
之前正常流程是需要执行一次计划任务,就会调用对应bean的方法,这里执行一次 Test 计划任务
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411105856244-204620663.png
发现请求到 /monitor/job/run 路径下https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411105943463-913239357.png
找到该路径
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411110212518-1841044837.png
并打上断点,跟进到服务层
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411110134134-1971741800.png
进入到服务层,首先通过ID取出之前存入的对象
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411113453233-426723077.png
跳到下一个断点,发现这里实际执行的是 StdScheduler 类的 triggerJob 方法
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411113806868-1157766165.png
找到这个方法,发现是第三方包 quaryz 2.3.2 版本
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411113910903-1694802206.png
去 google 查找到一下这个第三方包的文档,看实现的是什么功能,找到官方文档
https://javadoc.io/doc/org.quartz-scheduler/quartz/latest/index.html
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411114138127-1533027362.png
就是以代理的方式去执行类的方法
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411114345423-1634939592.png
执行成功,这里在完成最后一步操作,根据公众号发送的最后一个包,去请求文件
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411114617936-1203450383.png
尝试访问,下载成功(这里这个文件实际存放在 C://Users/Test01/Desktop/target.txt ,之前由于我的环境没有E盘,所以前面的计划任务传参都要改成C盘,重新编辑计划任务后再执行一次即可,然后再下载)
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411115350873-1514750589.png
但为什么这里要传 info.xml:.zip 呢,跟进到请求地址 /common/download/resource
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411115736018-1963790871.png
这里有过滤,打上断点,进入一个判断
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411115936425-799851879.png
跟进 checkAllowDownload 方法,这里进行 .. 过滤,还进行了后缀名过滤
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411120033960-2010206023.png
查看 DEFAULT_ALLOWED_EXTENSION 后缀名白名单有哪些
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411120101189-294636424.png
这里的匹配规则是取最后一个 . 的后缀,所以取得 zip 去比较,在范围内,绕过白名单
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411120249754-1815525764.png
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411120410443-928134956.png
回到最上层方法,这里通过 RouYiConfig.getProfile() 获取我们之前计划任务修改的地址,也就是 C盘的指定地址
然后在通过 StringUtils.substringAfter() 拼接 路径 和 文件名
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411120620450-1294709504.png
跟进 substringAfter 方法,这里做了文件名判断,判断是否包含 / profile 路径名称,找不到对应路径所以返回 -1,所以返回为空
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411133138057-2078866207.png
所以和空拼接后根据路径没有变化
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411133237915-753298006.png
然后以最后一个 / 符号拆分出来文件名字
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411133403091-791353239.png然后跟进 writeBytes 方法,其实该方法就是去请求 downloadPath 中对应的文件路径,并写入到 reponse 中并返回给用户
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411133514754-869172482.png
进入 write Bytes 方法,请求文件,并以 Stream 流的方式写入到 os 中,os 就是 reponse 用于返回给前端用户。
 
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411133825764-1245915204.png
所以这里的请求只需要绕过白名单即可,请求文件名即可以是 xxx.html、yyy.ppt、zzz.mp4,便可绕过白名单判断,并绕过文件名的路径判断,返回为空后去拼接根目录,便完成任意文件下载漏洞。
更改路径名称一样也能访问该文件
https://img2023.cnblogs.com/blog/1728854/202304/1728854-20230411134943977-1523308264.png
芜湖完工!

总结:这种漏洞不可能通过黑盒测试出来,只能通过白盒测试,并且需要对站点代码和组件使用非常熟悉,才能通过代码审计的方式挖掘此洞。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 若依 4.7.6 版本 任意文件下载漏洞(审计复现)