若依系统版本小于等于4.6.1的时候存在SQL注入漏洞,这个SQL注入漏洞挺有意思的。
初学者在学习SQL注入的时候往往加个单引号闭合一下,如果没有报错就测试下一个点了。然而这种方式最终将会错过许多好玩的漏洞。
一位前辈曾和我说过,SQL注入漏洞最终落脚点可不是单引号这么简单,而是“闭合”。“闭合”两个字后面一直印在我脑海里,跟着我去看待每个SQL注入漏洞。而本文要讲的IN注入,更是将闭合思想发挥的淋漓尽致。
除此之外,我们初学者入门SQL注入的时候,往往面对的都是SELECT开头的查询语句的注入,比较少面对譬如UPDATE、INSERT、DELETE这种动作词的注入。本文研究的漏洞恰好是UPDATE这种动作词的注入,非常值得一看。
若依系统使用的是SpringBoot架构,初学者往往会在这种架构的各种文件中迷失。本文会简单阐述如何从SQL沿着架构找到参数的输入点。当然你最好对MVC的这种架构设计思想有一点了解。
Mybatis框架SQL注入的查找方式
UPDATE注入
IF注入
SpringBoot架构查找参数输入点
01
在Mybatis中,参数输入有两种方式,分别为#{param}和${param}。这里面#{param}会使用占位符号(会进行预编译),而${param}会和原生的SQL语句进行拼接,因此使用${param}得到参数的方法存在SQL注入。
在若依项目里面使用ctrl+shift+f进行查找,注意查找的范围选择XML文件,因为mybatis的SQL映射都写在XML文件下。
可以看到这边存在很多个${param}的SQL语句,我们第一个漏洞点就存在ancestors这个参数里面。点过去跟进查询详细语句。
#这段XML转化为SQL语句应该是这样的:
update
sys_dept update_time =
sysdate
()
where
dept_id
in
(${ancestors})
update
sys_dept update_time =
sysdate
(),
status
=
#{status} where dept_id in (${ancestors})(如果status不为空的话)
update
sys_dept update_time =
sysdate
(),update_by =
#{updateBy}where dept_id in (${ancestors})(如果updateBy不为空的话)
但是不管怎么样,ancestors都是可控的,因此是存在SQL注入的。
02
在springboot架构中,有以下几种方式执行SQL操作:
1、业务层调用DAO层
2、controller调用service层间接调用DAO层
3、controller直接调用DAO层
DAO层用来存放mapper接口,mapper作用为访问数据库,向数据库发送sql语句,完成数据的增删改查功能,通常将其实现为接口,内部声明的方法将会于mapper层中的对应数据库函数关联 。简而言之就是就是执行SQL语句相关的。
比如下列文件就是DAO层文件:
控制请求和响应,负责前后端交互。controller层主要调用Service层里面的接口控制具体的业务流程,不会在其中编写大量逻辑代码,同时也会接受并处理一些HTTP参数,例如session。控制层一大特征就是前面会加@controller。控制层我们可以看到接收了什么参数,什么参数是可控的。
业务逻辑层,完成功能的设计。和DAO层一样都是先设计接口,再创建要实现的类,然后在配置文件中进行配置其实现的关联。service的impl是把mapper和service进行整合的文件 封装Service层的业务逻辑有利于业务逻辑的独立性和重复利用性。在service的实现类上要加注解@Service,否则会出现无法扫描识别 。
跟进SERVICE的方法便能够到达SERVICE层,比如下面这个方法:
具体实现业务在接口下:
这个就是具体实现业务
在这个类的最上面写着@Service,说明这个就是SERVICE层。
所以我们可以看见,若依采用的是第2种方式调用SQL语句的,即controller调用Service层间接调用DAO层。
03
SQL注入要求输入参数可控,因此下面得寻找参数的输入点。
以这个SQL语句的id作为检索条件搜索(注意要改成搜索.java文件结尾的方式),如下图:
通过上诉搜索.java结尾的文件直接到达DAO层了。
点击“1 usage”跟进第二个调用这个SQL的语句:
这个时候我们已经来到了Service层,可以看见输入的参数是dept,那么dept是什么,它可控吗?继续向上跟进。
这个时候来到了controller层:
URL路径
我们具体看一下这个controller层的这个方法前面的标签:
第一个是用于日志文件的,第二个用于权限控制的,第三个则是我们比较关心的,就是url入口。不过这里的/edit是相对url,要看完整url,还有往上面翻一下:
因此完整的url路径应该将两个拼接起来:/system/dept/edit
输入参数
要看输入什么参数得看这个。输入的数据类型为SysDept,前面的@Validated是检测数据是否有效的。
我们跟进SysDept,可以发现,有ancestors这个参数,而且还是String的,且没有任何过滤。那么这个SQL注入就能够利用了。
04
回归最开始的SQL语句
update
sys_dept update_time =
sysdate
()
where
dept_id
in
(${ancestors})
这里涉及到两个知识点:
1.SQL动词注入:SQL语句为UPDATE、INSERT、DELETE等动词的时候,一般使用报错注入。
2.IN注入:可控参数在in后面可以使用括号进行闭合,而不是单引号。
结合上面两点思路,我们可以构造payload如下:
update
sys_dept update_time =
sysdate
()
where
dept_id
in
(
0
)
or
(extractvalue(
1
,
concat
((
select
+
user
()))))
即
ancestors
=
0
)or(extractvalue(
1
,concat((select+user())))
那么接下来就验证这个payload的。我们一般我直接去漏洞的url,而是去对应的功能点。从上面函数可以看出这个功能点是修改部门的,因此我们定位到如下功能点:
使用Burp Suite抓取报文:
没有ancestors这个参数,我们手动输入。
发现利用失败,最后排查发现是Content-Length的原因,这个字段得和上传参数一样。
直接使用python:
运行后依旧不行。
结合其它博主的payload,我发现parentId不能使用系统已经有的,而要使用没有的。
从比较抽象的层面来看,该SQL语句的目的是为了更新父部门的状态。而parentId说明现在更新的部门的父部门id,ancestors这个参数有祖先的意思。因此两个参数可能会互相影响。说明这两个参数是否冲突调试起来比较麻烦,就不做进一步调试了。
但是我们可以得到一点经验:降低输入参数的关联性,避免冲突。
最后将parentId改为不存在的1
Content-Length改为 261,然后发送报文:
成功!
05
①使用${查找Mybatis的SQL注入点。
②了解SpringBoot的架构,找到可控的SQL注入输入点。
③UPDATE使用报错注入。
④IN注入使用括号闭合。
⑤尽量降低参数之间的关联性,避免影响结果。
⑥记得修改Content-Length,确保和参数长度一致。
// 作者:IntSheep
原文始发于微信公众号(赛博游民营):Java代码审计:SQL注入中的Update、In注入(若依CMS|Mybatis|Spring|新手向)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论