作者:madneal@平安银行应用安全团队

MyBatis 是一种持久层框架,介于 JDBC 和 Hibernate 之间。通过 MyBatis 减少了手写 SQL 语句的痛苦,使用者可以灵活使用 SQL 语句,支持高级映射。但是 MyBatis 的推出不是只是为了安全问题,有很多开发认为使用了 MyBatis 就不会存在 SQL 注入了,真的是这样吗?使用了 MyBatis 就不会有 SQL 注入了吗?答案很明显是 NO。 MyBatis 它只是一种持久层框架,它并不会为你解决安全问题。当然,如果你能够遵循规范,按照框架推荐的方法开发,自然也就避免 SQL 注入问题了。本文就将 MyBatis 和 SQL 注入这些恩恩怨怨掰扯掰扯。(注本文所说的 MyBatis 默认指的是 Mybatis3)

起源

写本文的起源主要是来源于内网发现的一次 SQL 注入。我们发现内网的一个请求的 keyword 参数存在 SQL 注入,简单地介绍一下需求背景。基本上这个接口就是实现多个字段可以实现 keyword 的模糊查询,这应该是一个比较常见的需求。只不过这里存在多个查询条件。经过一番搜索,我们发现问题的核心处于以下代码:

public Criteria addKeywordTo(String keyword) {

StringBuilder sb = new StringBuilder();

sb.append("(display_name like '%" + keyword + "%' or ");

sb.append("org like '" + keyword + "%' or ");

sb.append("status like '%" + keyword + "%' or ");

sb.append("id like '" + keyword + "%') ");

addCriterion(sb.toString());

return (Criteria) this;

}

很明显,需求是希望实现 diaplay_name, org, status 以及 id 的模糊查询,但开发在这里自己创建了一个 addKeywordTo 方法,通过这个方法创建了一个涉及多个字段的模糊查询条件。有一个有趣的现象,在内网发现的绝大多数 SQL 注入的注入点基本都是模糊查询的地方。可能很多开发往往觉得模糊查询是不是就不会存在 SQL 注入的问题。分析一下这个开发为什么会这么写,在他没有意识到这样的写法存在 SQL 注入问题的时候,这样的写法他可能认为是最省事的,到时直接把查询条件拼进去就可以了。以上代码是问题的核心,我们再看一下对应的 xml 文件:

and ${criterion.condition}

and ${criterion.condition} #{criterion.value}

and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}

and ${criterion.condition}

#{listItem}

select

distinct

from user

order by ${orderByClause}

我们再回过头看一下上面 JAVA 代码中的 addCriterion 方法,这个方法是通过 MyBatis generator 生成的。

protected void addCriterion(String condition) {

if (condition == null) {

throw new RuntimeException("Value for condition cannot be null");

}

criteria.add(new Criterion(condition));

}

这里的 addCriterion 方法只传入了一个字符串参数,这里其实使用了重载,还有其它的 addCriterion 方法传入的参数个数不同。这里使用的方法只传入了一个参数,被理解为 condition,因此只是添加了一个只有 condition 的 Criterion。现在再来看 xml 中的 Example_Where_Clause,在遍历 criteria 时,由于 criterion 只有 condition 没有 value,那么只会进去条件 criterion.noValue,这样整个 SQL 注入的形成就很清晰了。

and ${criterion.condition}

正确写法

既然上面的写法不正确,那正确的写法应该是什么呢?第一种,我们可以用一种非常简单直接的方法,在 addKeywordTo 方法里面 对 keword 进行过滤,这样其实也可以避免 SQL 注入。通过正则匹配将 keyword 里面所有非字母或者数字的字符都替换成空字符串,这样自然也就不可能存在 SQL 注入了。

keyword = keyword.replaceAll("[^a-zA-Z0-9\s+]", "");

但是这种写法并不是一种科学的写法,这样的写法存在一种弊端,就是如果你的 keyword 需要包含符号该怎么办,那么你是不是就要考虑更多的情况,是不是就需要添加更多的逻辑判断,是不是就存在被绕过的可能了?那么正确的写法应该是什么呢?其实 mybatis 官网 已经给出了 Comple Queries 的范例:

TestTableExample example = new TestTableExample();

example.or()

.andField1EqualTo(5)

.andField2IsNull();

example.or()

.andField3NotEqualTo(9)

.andField4IsNotNull();

List field5Values = new ArrayList();

field5Values.add(8);

field5Values.add(11);

field5Values.add(14);

field5Values.add(22);

example.or()

.andField5In(field5Values);

example.or()

.andField6Between(3, 7);

上面等同的 SQL 语句是:

where (field1 = 5 and field2 is null)

or (field3 <> 9 and field4 is not null)

or (field5 in (8, 11, 14, 22))

or (field6 between 3 and 7)

现在让我们将一开始的 addKeywordTo 方法进行改造:

public void addKeywordTo(String keyword, UserExample userExample) {

userExample.or().andDisplayNameLike("%" + keyword + "%");

userExample.or().andOrgLike(keyword + "%");

userExample.or().andStatusLike("%" + keyword + "%");

userExample.or().andIdLike(keyword + "%");

}

这样的写法才是一种比较标准的写法了。or() 方法会产生一个新的 Criteria 对象,添加到 oredCriteria 中,并返回这个 Criteria 对象,从而可以链式表达,为其添加 Criterion。这样添加的的 Criteria 就是包含 condition 以及 value 的,在做条件查询的时候,就会进入到 criterion.singleValue 中,那么 keyword 参数只会传入到 value 中,而 value 是通过 #{} 传入的。

and ${criterion.condition} #{criterion.value}

总结以下,导致这个 SQL 注入的原因还是开发没有按照规范来写,自己造轮子写了一个方法来进行模糊查询,殊不知带来了 SQL 注入漏洞。其实,Mybatis generator 已经为每个字段生成了丰富的方法,只要合理使用,就一定可以避免 SQL 注入问题。

使用 #{} 可以避免 SQL 注入吗

如果你猛地一看到这个问题,你可能会觉得迟疑?使用 #{} 就可以彻底杜绝 SQL 注入么,不一定吧。但如果你仔细分析一下,你就会发现答案是肯定的。具体的原因让我和你娓娓道来。

首先我们需要先搞清楚 MyBatis 中 #{} 是如何声明的。当参数通过 #{} 声明的,参数就会通过 PreparedStatement 来执行,即预编译的方式来执行。预编译你应该不陌生,因为在 JDBC 中就已经有了预编译的接口。这也对应了开头文中我们提到的一点,Mybatis 并不是能解决 SQL 注入的核心,预编译才是。预编译不仅可以对 SQL 语句进行转义,避免 SQL 注入,还可以增加执行效率。Mybatis 底层其实也是通过 JDBC 来实现的。以 MyBatis 3.3.1 为例,jdbc 中的 SqlRunner 就设计到具体 SQL 语句的实现。

以 update 方法为例,可以看到就是通过 JAVA 中 PreparedStatement 来实现 sql 语句的预编译。

public int update(String sql, Object... args) throws SQLException {

PreparedStatement ps = this.connection.prepareStatement(sql);

int var4;

try {

this.setParameters(ps, args);

var4 = ps.executeUpdate();

} finally {

try {

ps.close();

} catch (SQLException var11) {

;

}

}

return var4;

}

值得注意的一点是,这里的 PreparedStatement 严格意义上来说并不是完全等同于预编译。其实预编译分为客户端的预编译以及服务端的预编译,4.1 之后的 MySql 服务器端已经支持了预编译功能。很多主流持久层框架(MyBatis,Hibernate)其实都没有真正的用上预编译,预编译是要我们自己在参数列表上面配置的,如果我们不手动开启,JDBC 驱动程序 5.0.5 以后版本 默认预编译都是关闭的。需要通过配置参数来进行开启:

jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true&cachePrepStmts=true

数据库 SQL 执行包含多个阶段如下图所示,但我们这里针对于 SQL 语句客户端的预编译在发送到服务端之前就已经完成了。在服务器端主要考虑的就是性能问题,这不是本文的重点。当然,每一个数据库实现的预编译方式可能都有一些差别。但是对于防止 SQL 注入,在 MyBatis 中只要使用 #{} 就可以了,因为这样就会实现 SQL 语句的参数化,避免直接引入恶意的 SQL 语句并执行。

MyBatis generator 的使用

对于使用 MyBatis,MyBatis generator 肯定是必不可少的使用工具。MyBatis 是针对 MyBatis 以及 iBATIS 的代码生成工具,支持 MyBatis 的所有版本以及 iBATIS 2.2.0 版本以上。因为在现实的业务开发中,肯定会涉及到很多表,开发不可能自己一个去手写相应的文件。通过 MyBatis generator 就可以生成相应的 POJO 文件、 SQL Map XML 文件以及可选的 JAVA 客户端代码。常用的使用 MyBatis generator 的方式是直接通过使用 Maven 的 mybatis-generator-maven-plugin 插件,只要准备好配置文件以及数据库相关信息,就可以通过这个插件生成相应代码了。

connectionURL="jdbc:mysql://localhost:3306/mybaits_test"

userId="xxx"

password="xxx">

在这里我想强调的是一个关键参数的配置,即 targetRuntime 参数。这个参数有2种配置项,即 MyBatis3 和 MyBatis3Simple,MyBatis3 为默认配置项。MyBatis3Simple 只会生成基本的增删改查,而 MyBatis3 会生成带条件的增删改查,所有的条件都在 XXXexample 中封装。使用 MyBatis3 时,enableSelectByExample,enableDeleteByExample,enableCountByExample 以及 enableUpdateByExample 这些属性为 true,就会生成相应的动态语句。这也就是我们上述 Example_Where_Clause 生成的原因。

如果使用配置项 MyBatis3Simple,那么生成的 SQL Map XML 文件将非常简单,只包含一些基本的方法,也不会产生上面的动态方法。可以这么说,如果你使用 MyBatis3Simple 话,并且不额外改造,因为里面所有的变量都是通过 #{} 引入,就不可能会有 SQL 注入的问题。但是现实业务中往往涉及到复杂的查询条件,而且一般开发使用的都是祖传配置文件,所以到底是使用 MyBatis3 还是 MyBatis3Simple,还是需要具体问题,具体看待。不过如果你是使用默认配置,你就需要当心了,谨记一点,外部传入的参数是极有可能是不安全的,是不可以直接引入处理的。意思到这一点,就基本可以很好地避免 SQL 注入问题了。

我创建了一个 Github 仓库 mb-generator,这个仓库里面的 mybatis3 分支以及 mybatis3simple 分支分别是使用不同的配置项生成的代码,你可以去看一看生成的代码具体差别有哪一些,可以看一看使用不同配置项的具体差别。

总结

这篇文章从内网的一个 SQL 注入漏洞引发的对 MyBatis 的使用问题思考,对 MyBatis 中 #{} 工作的原理以及 Mybatis generator 的使用多个方面做了进一步的思考。可以总结以下几点:

能不使用拼接就不要使用拼接,这应该也是避免 SQL 注入最基本的原则

在使用 ${} 传入变量的时候,一定要注意变量的引入和过滤,避免直接通过 ${} 传入外部变量

不要自己造轮子,尤其是在安全方面,其实在这个问题上,框架已经提供了标准的方法。如果按照规范开发的话,也不会导致 SQL 注入问题

可以注意 MyBatis 中 targetRuntime 的配置,如果不需要复杂的条件查询的话,建议直接使用 MyBatis3Simple。这样可以更好地直接杜绝风险,因为一旦有风险点,就有发生问题的可能。

Reference

mybatis比mysql安全吗_MyBatis 和 SQL 注入的恩恩怨怨相关推荐

  1. mysql注入式攻击_mybatis的sql中使用$会出现sql注入示例

    mybatis的sql中使用$会出现sql注入示例: 模拟简单登录场景: 页面代码: functionlogin(){//sql注入 var user ={ username :"'李雪雷3 ...

  2. 【Mysql优化安全】防止sql注入

    [Mysql安全]防止sql注入 (1)什么是sql注入 (2)寻找sql注入的方法 (3)mybatis是如何做到防止sql注入的 (3.1)sql对比 (3.2)简单分析 (3.3)底层实现原理 ...

  3. mysql node null_node-mysql中防止SQL注入的方法总结

    SQL注入简介 SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编程时的疏忽,通过SQL语句,实现无帐号登录,甚至篡改数据库. node-mysql中防止S ...

  4. mysql 时间盲注语句,sql注入学习记录(5)-基于时间延迟的SQL盲注

    上次说到了sql注入中的基于报错盲注的基本的方法. 今天说一说报错盲注 基于时间延时的SQL盲注 使用时间延时注入的场景: 1.不能使用union select 联合查询方式注入 2.有些网站没有回显 ...

  5. MySQL攻略 - JDBC程序SQL注入,PreparedStatement接口详解与案例练习,JDBC相关API小结

    文章目录 SQL注入 Statement详解 基本介绍 Navicat演示SQL注入 JDBC演示SQL注入 PreparedStatement详解 基本介绍 预处理好处 预处理案例(selete语句 ...

  6. mambo 服务商规定的mysql密码,Mambo com_content远程SQL注入漏洞

    发布日期:2005-06-16更新日期:2005-06-16受影响系统: Mambo Mambo Open Source <= 4.5.2.2不受影响系统: Mambo Mambo Open S ...

  7. mybatis与mysql的优点_MyBatis的优缺点以及特点

    特点: mybatis是一种持久层框架,也属于ORM映射.前身是ibatis. 相比于hibernatehibernate为全自动化,配置文件书写之后不需要书写sql语句,但是欠缺灵活,很多时候需要优 ...

  8. mybatis对mysql if函数_Mybatis使用mysql函数if出现错误

    在mybatis的mapper文件中使用IF函数的时候,莫名其妙的报错,但是在sql语句是正确的,可以在navcat中运行,但是在mapper文件中使用就是报错,查阅资料发现说什么情况的都有,有说缺少 ...

  9. mybatis与mysql的优点_mybatis和hibernate比较_区别_优缺点

    前言 Hibernate与Mybatis都是流行的持久层开发框架,但Hibernate开发社区相对多热闹些,支持的工具也多,更新也快,当前最高版本4.1.8.而Mybatis相对平静,工具较少,当前最 ...

  10. mybatis查询mysql blob字段_mybatis+spring+mySql 处理Blob字段(报错:data too long for column 'descrip')...

    通过Spring+Mybatis+Mysql搭建了一个框架,在向MYSQl插入图片信息时,将其保存为BLOB类型到数据库. 前台JAVA代码如下: @RequestMapping("/zdf ...

最新文章

  1. Argus(ZOJ Problem Set - 2212)(优先队列)
  2. C++_new创建的数组作为参数传递
  3. java unit test怎么写_Java J Unit Test
  4. parameter与localparam使用区别
  5. threejs加载obj模型_倾斜摄影三维模型几种常见的格式,你能说出哪些?
  6. Docker安装gbase8s数据库教程
  7. unalias 命令
  8. 小提琴1234567位置图解_小提琴入门指法(小提琴1234567位置图解)
  9. webex java插件_Linux安装Java和Webex
  10. android+微博点赞动画,模仿微博点赞动画
  11. 背包问题——贪婪算法
  12. 大猩猩优化算法(Matlab代码实现)
  13. android 崩溃原因,Android中导致小米系列手机直接崩溃的主要原因。
  14. 新能源汽车OTA升级中的UDS通信分析
  15. maven profiles勾选不了
  16. Nginx启动报错:error while loading shared libraries: libpcre.so.1
  17. iOS系统访问权限大全
  18. Unity3d中异形屏的适配
  19. metaspolit提示Exploit failed: You must select a target.
  20. form编译报错:ORA-12162: TNS:net service name is incorrectly specified解决办法

热门文章

  1. CSS3+HTML5特效5 - 震动的文字
  2. div+css总结—FF下div不设置…
  3. jsp:setProperty getProperty标签的使用
  4. Java出现OutOfMemoryError
  5. printf格式控制符的完整格式
  6. 一些控制鼠标的例子!
  7. Oracle递归sql
  8. @EnableWebMvc引发的swagger-ui.html的灾难
  9. MyBatis-Plus updateById方法更新不了空字符串/null解决方法
  10. MySQL的条件判断函数