审计SQL注入时的小tips
一、前景提要
起因是因为审计完rbac项目之后,把审计报告发到博客。过了一段时间有位师傅跟我说这个项目还有一个SQL注入我没有审计到,我寻思这个项目用的Mybatis框架,我记得SQL语句用$拼接参数的地方我都审了,不应该有遗漏诶。经过一番交流之后发现确实遗漏了一个注入点,但这个注入点的利用也比较巧,所以写一篇博客来记录和分享一下这个注入点的挖掘和利用。
二、代码审计
1、源码分析
项目就还是我以前文章的rbac项目,所以就不分析其他的了,我们直接来看有问题的Mapper文件代码,如下
使用了${ancestors}拼接参数,追踪到Dao层,如下
追踪到实现类,如下
可以看到在实现类的updateParentDeptStatus方法调用了updateDeptStatus方法,我们再追踪到调用updateParentDeptStatus方法的位置,如下
看到是实现类的updateDept方法调用了updateParentDeptStatus方法,继续追踪,来到Controller层
可以看到Controller层代码处理了传入的Mydept对象,处理的过程中调用updateDept方法
同时Mydept实体类的代码如下
现在我们理一下上面的输入流转过程,如下
- 1、用户输入,传入Mydept对象,命名为为dept
- 2、Controller层调用updateDept(dept)
- 3、updateDept方法调用updateParentDeptStatus(dept)
- 4、updateParentDeptStatus方法调用了updateDeptStatus(dept)
- 5、进入DeptDao
- 6、Mabatis获取传入的Dept实例的ancestors属性值,并执行对应的update语句
咋一看是不是一个很简单的注入,但是仔细看看第四步的代码,如下
获取传入的dept实例的dept_id属性值,并根据这个id值从数据库获取对应的dept实例,再将数据库获取的dept赋值为dept,即覆盖了我们传入的dept实例
那这里被覆盖之后的dept的ancestors属性值还会是我传入的dept属性值吗?当然就不是了,而是根据传入的id值从数据库中取出来的
说明了什么?说明针对这个输入流转,我们的ancestors值是不可控的,所以从这个点来看,是不存在注入的(我第一次审计就是看到这就认为不存在SQL注入,就没继续了,所以错过了这个漏洞)
这里为什么要强调“从这个点来看”?
这里就引出一个平时审计SQL注入漏洞时需要注意的点了,当某个存在SQL注入的参数在当前输入流转不可控时,可以找找其他地方,看能否通过其他功能修改该参数,从而使该参数可控
那怎么找呢?找能够控制该参数的对应方法即可,例如这里参数不可控的原因是因为参数是从数据库取出来的,那我们就可以找找什么地方可以修改数据库中的这个值,即在对应的mapper文件中找找insert语句和update语句
这里共有三个update语句,并没有insert,所以下面我们来逐个看看这三个update语句
2、漏洞定位
2.1、第一个update
第一个update语句如下
可以看到这个update语句的作用就是设置指定id的部门的ancestors为指定值
经过追踪,发现该方法传入的ancestors只能是如下
String newAncestors = parentInfo.getAncestors() + "," + parentInfo.getDeptId();
即{上级部门ancestors,上级部门id}
显然我们要控制此处的ancestors为恶意payload是不可能的(因为部门id我们是不可控的),所以这个点放弃
2.2、第二个update
第二个update语句如下
将数据库中指定id的dept信息换为传入的dept信息,其中就包含了ancestors参数,所以只要我们能够控制传入dept实例的ancestors属性值,就能够修改数据库中对应的ancestors,造成SQL注入漏洞
所以我们向上追踪一下,先来到Dao层,如下
来到实现类
可以看到实现类中,传入的dept对象如果有上级部门,并且当前dept对象不为空,就会进入到if判断逻辑内部,这里if判断逻辑我们再分析第一个update时也分析过了,会修改我们传入的dept对象的ancestors属性值
如果我们想要插入恶意ancestors,自然就不能让程序修改我们输入的ancestors属性值,也就是不进入这个if判断逻辑,就不能满足“有上级部门,并且当前dept对象不为空”这个条件
首先是dept对象不为空
- 我们传入的dept对象因为要带有恶意ancestors参数值,所以也肯定不会为空
其次就是有上级部门
- 所以我们要尽量保证我们传入的dept对象没有上级部门,这样就不会进入if内部导致输入的ancestors被程序修改
之后程序就会调用updateDept直接替换dept对象的信息到数据库中,我们继续向上追踪,来到Controller层
可以看到Dept对象来源于用户的请求,经过一系列逻辑判断,再调用实现类,所以我们请求该Controller方法映射的接口时,带上ancestors参数即可。并且通过观察该方法的注解,发现该方法对应程序中的“修改部门”功能
所以经过上面的分析,我们确定这个位置是可以控制数据库中的ancestors值的,只需要在“修改部门”的请求中带上ancestors参数,并且当前部门没有上级部门时,我们输入的ancestors就会覆盖数据库中ancestors参数值,即实现了ancestors参数可控
又因为刚才我们发现的SQL语句使用$拼接了ancestors参数的值,并且ancestors参数的值是从数据库中获取的,如下
所以就成功了达到了SQL注入的形成条件,初步判断此处存在SQL注入,等待后面验证
2.3、第三个update
第三处update语句如下
这个update语句根本没涉及到ancestors参数,所以无需分析
三、漏洞验证
因为上面我们分析出这个漏洞点位于“修改部门‘功能处,所以我们来到部门管理页面
选定一个没有上级部门的部门,并点击编辑,如下
开启抓包,再点击提交按钮,发起请求,抓到如下请求包
发现请求没有携带ancestors参数,但是我们刚才分析此处是可以传入ancestors参数的,因为接收的是Mydept类,如下
所以Mydept类有的属性都可以接收,My_dept类代码如下
所以构造一个ancestors参数,如下
再结合存在漏洞的Mapper文件传入恶意payload进行闭合,如下
闭合之后发送请求包,效果如下
SQL时间注入验证成功,所以该位置的确存在SQL注入漏洞
四、总结
如果能够导致SQL注入的参数在对应的输入流转中不可控,可以找找其他的输入流转,看看有没有能够控制该参数的功能或方法,使用这些方法控制该参数,达到参数可控的效果,从而执行恶意SQL语句,成功利用SQL注入漏洞