Mybatis源码阅读番外篇——Sql注入问题

【系列目录】
Mybatis源码阅读之一——工厂模式与SqlSessionFactory

Mybatis源码阅读之二——模板方法模式与Executor

Mybatis源码阅读之三——JDBC解析与Mybatis封装

Mybatis源码阅读之四——装饰器模式与Mybatis中的各种Cache

Mybatis源码阅读之六——数据库连接池实现与hikariCP简析

Mybatis源码阅读之八——代理模式与Mybatis插件(pagehelper为例)

Mybatis源码阅读之九——适配器模式与日志模块

Mybatis源码阅读之九——适配器模式与日志模块

Mybatis源码阅读之十——Mybatis中优雅的责任链模式

Mybatis源码阅读之十一——反射


Sql注入是前些年盛行的一种服务攻击手段,是发生于应用程序与数据库层的安全漏洞。

注入举例

原功能SQL如下,这是一个根据username查询的sql。

select * from admin where username='$username'

一个正常的请求如下:

select * from admin where username="zhangsan"

但是如果没有预防措施,这里的参数$username有可能篡改,造成数据泄漏/恶意错误/恶意阻塞等问题。
payload:

username=admin' union select * from sys_user

恶意SQL:

select * from admin where username='admin' or 1 union select * from sys_user

注入手段

攻击者是如何知道某个接口有SQL可注入漏洞。

错误回显

通过页面传参测试生成如下Sql:

payload:
  url?username='"
select * from admin where
username='"

显然这是一个左单引号有双引号的错误sql,如果接口返回SQL语法的错误信息,那么就可以明确的知道这个接口没有做SQL注入的防御措施。

那我们可能会说,既然错误信息提示了攻击者,那我们把SQL错误的内部信息拦截掉,不展示到页面不就好了?
答案没那么简单。

盲注

没有错误信息,攻击者还可以通过盲注进行接口是否可注入的验证。
构造简单的条件语句,根据返回页面是否发生变化来判断SQL语句是否得到执行。
比如:

payload:  
  url?username=真实username and 1=2

当我们关掉了错误回显,以上sql的执行显然会返回一个空结果。但是做了防注入处理的执行也可能返回空结果,所以需要进一步验证。

payload:  
  url?username=真实username and 1=1

如果这一次能够正常看到数据,那么说明漏洞存在。

Time Attach

除了盲注还有一种判断方式,利用BENCHMARK函数或SLEEP函数。

payload:  
  url?id=1 and (select sleep(5))
select * from admin where
id=1 and (select sleep(5))

如果接口响应达到我们指定的睡眠秒数,那么可以认定存在漏洞。

参考: 《白帽子讲Web安全》

如何防止注入

一条sql的行程

在一个使用Mybatis的Web应用中,sql会经过如图过程,可见,哪里会对SQL进行参数解析赋值,哪里就可能产生注入问题。

Mybatis(或其他JDBC封装)层面防注入

那么这里就不只是要考虑对Mysql服务本身的注入,还要考虑使用Mybatis等对JDBC功能进行了封装的框架,它们可能导致的注入问题。

占 位 符 , 这 是 一 个 常 见 面 试 题 , 这 里 的 漏 洞 无 法 在 数 据 库 层 面 防 范 。 ∗ ∗ 因 为 它 发 生 在 M y b a t i s 对 S Q L 的 解 析 过 程 中 , 会 将 {}占位符,这是一个常见面试题,这里的漏洞无法在数据库层面防范。 **因为它发生在Mybatis对SQL的解析过程中,会将 MybatisSQL{}替换成对应的参数值,得到一个完整的SQL,然后使用完整SQL再请求数据库。**

我们应该尽量使用#{},#{}不会在Mybatis中直接替换成完整SQL,而会使用数据库提供的预编译功能来防止SQL注入。

Mysql层面防注入

为了从根本上避免上述注入问题,我们应该使用数据库本身提供的预编译机制。
JDBC对预编译也提供了对应的抽象接口(PerparedStatement):

预编译实现参数化查询

什么是数据库预编译?

将一次Sql拆分为多次,第一次传入Sql,不包含参数;第二次传入参数,执行Sql。

一个插入sql的预编译使用流程如下:

mysql> prepare goodstest from 'insert into goods values(?,?)';
Query OK, 0 rows affected (0.00 sec)
Statement prepared

mysql> set @a=1,@b='测试商品';
Query OK, 0 rows affected (0.00 sec)

mysql> execute ins using @a,@b;
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0

为什么预编译可以防止Sql注入?

因为数据库(Mysql)服务本身在第一次请求后已经知道了这个SQL本身结构,知道?的位置是参数,那么数据库就可以对第二次传入的参数做限制,无论第二次过来的是什么语句,包含什么关键字都没关系,我认为你就是个字符串或其他基本类型。

不可以预编译解决的注入-白名单

预编译不是万能的,有些场景下就不可以使用,比如order by {参数变量}。

为什么order by {参数变量}不能使用预编译,不能使用#{}?

因为预编译本身是对传入的参数做了字符串化的操作,也就是将传入的参数前后都加上一个引号,这种处理对于参数来说自然是可以的,因为数据库会进行自动的类型转换,但是对于order by却不可以,它后面跟的不是参数,而是表字段/关键字。

如果带上引号成了order by ‘username’,那username就是一个字符串不是字段名了,那么排序自然不成立。对于mysql来说,它将会无视这个order by条件。

那么我们就可以得出一个结论:

凡是字符串但又不能加引号的位置都不能参数化,包括sql关键字、库名表名字段名函数名等等。

那么这种情况下,该如何解决呢?
在应用服务中手动添加白名单,比如我们可以根据id,name排序,那么就在业务代码中过滤判断进行拦截。
当然白名单也可以做的优雅一些,我们可以借助数据库可查询库表字段的功能来实现。


原创不易,文章下方点个在看,作者更有动力!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值