一、前景提要

起因是因为审计完rbac项目之后,把审计报告发到博客。过了一段时间有位师傅跟我说这个项目还有一个SQL注入我没有审计到,我寻思这个项目用的Mybatis框架,我记得SQL语句用$拼接参数的地方我都审了,不应该有遗漏诶。经过一番交流之后发现确实遗漏了一个注入点,但这个注入点的利用也比较巧,所以写一篇博客来记录和分享一下这个注入点的挖掘和利用。

二、代码审计

1、源码分析

项目就还是我以前文章的rbac项目,所以就不分析其他的了,我们直接来看有问题的Mapper文件代码,如下

img

使用了${ancestors}拼接参数,追踪到Dao层,如下

img

追踪到实现类,如下

img

可以看到在实现类的updateParentDeptStatus方法调用了updateDeptStatus方法,我们再追踪到调用updateParentDeptStatus方法的位置,如下

img

看到是实现类的updateDept方法调用了updateParentDeptStatus方法,继续追踪,来到Controller层

img

可以看到Controller层代码处理了传入的Mydept对象,处理的过程中调用updateDept方法

同时Mydept实体类的代码如下

img

现在我们理一下上面的输入流转过程,如下

  • 1、用户输入,传入Mydept对象,命名为为dept
  • 2、Controller层调用updateDept(dept)
  • 3、updateDept方法调用updateParentDeptStatus(dept)
  • 4、updateParentDeptStatus方法调用了updateDeptStatus(dept)
  • 5、进入DeptDao
  • 6、Mabatis获取传入的Dept实例的ancestors属性值,并执行对应的update语句

咋一看是不是一个很简单的注入,但是仔细看看第四步的代码,如下

img

获取传入的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语句如下

img

可以看到这个update语句的作用就是设置指定id的部门的ancestors为指定值img

经过追踪,发现该方法传入的ancestors只能是如下

String newAncestors = parentInfo.getAncestors() + "," + parentInfo.getDeptId();

即{上级部门ancestors,上级部门id}

显然我们要控制此处的ancestors为恶意payload是不可能的(因为部门id我们是不可控的),所以这个点放弃

2.2、第二个update

第二个update语句如下

img

将数据库中指定id的dept信息换为传入的dept信息,其中就包含了ancestors参数,所以只要我们能够控制传入dept实例的ancestors属性值,就能够修改数据库中对应的ancestors,造成SQL注入漏洞

所以我们向上追踪一下,先来到Dao层,如下

img

来到实现类img

可以看到实现类中,传入的dept对象如果有上级部门,并且当前dept对象不为空,就会进入到if判断逻辑内部,这里if判断逻辑我们再分析第一个update时也分析过了,会修改我们传入的dept对象的ancestors属性值

如果我们想要插入恶意ancestors,自然就不能让程序修改我们输入的ancestors属性值,也就是不进入这个if判断逻辑,就不能满足“有上级部门,并且当前dept对象不为空”这个条件

  • 首先是dept对象不为空

    • 我们传入的dept对象因为要带有恶意ancestors参数值,所以也肯定不会为空
  • 其次就是有上级部门

    • 所以我们要尽量保证我们传入的dept对象没有上级部门,这样就不会进入if内部导致输入的ancestors被程序修改

之后程序就会调用updateDept直接替换dept对象的信息到数据库中,我们继续向上追踪,来到Controller层

img

可以看到Dept对象来源于用户的请求,经过一系列逻辑判断,再调用实现类,所以我们请求该Controller方法映射的接口时,带上ancestors参数即可。并且通过观察该方法的注解,发现该方法对应程序中的“修改部门”功能

所以经过上面的分析,我们确定这个位置是可以控制数据库中的ancestors值的,只需要在“修改部门”的请求中带上ancestors参数,并且当前部门没有上级部门时,我们输入的ancestors就会覆盖数据库中ancestors参数值,即实现了ancestors参数可控

又因为刚才我们发现的SQL语句使用$拼接了ancestors参数的值,并且ancestors参数的值是从数据库中获取的,如下

img

所以就成功了达到了SQL注入的形成条件,初步判断此处存在SQL注入,等待后面验证

2.3、第三个update

第三处update语句如下

img

这个update语句根本没涉及到ancestors参数,所以无需分析

三、漏洞验证

因为上面我们分析出这个漏洞点位于“修改部门‘功能处,所以我们来到部门管理页面image-20231222002258352

选定一个没有上级部门的部门,并点击编辑,如下

image-20231222002445871

开启抓包,再点击提交按钮,发起请求,抓到如下请求包

image-20231222002533285

发现请求没有携带ancestors参数,但是我们刚才分析此处是可以传入ancestors参数的,因为接收的是Mydept类,如下

image-20231222002745328

所以Mydept类有的属性都可以接收,My_dept类代码如下

image-20231222002853888

所以构造一个ancestors参数,如下

image-20231222003019247

再结合存在漏洞的Mapper文件传入恶意payload进行闭合,如下

image-20231222003117437

闭合之后发送请求包,效果如下

image-20231222003239159

SQL时间注入验证成功,所以该位置的确存在SQL注入漏洞

四、总结

如果能够导致SQL注入的参数在对应的输入流转中不可控,可以找找其他的输入流转,看看有没有能够控制该参数的功能或方法,使用这些方法控制该参数,达到参数可控的效果,从而执行恶意SQL语句,成功利用SQL注入漏洞