MyBatis SQL注入隐患及防范

什么是 MyBatis ?

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎全部的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

常见风险

状况一:

/* mapper设置  */
<select id="getUserbyId" resultType="model.Student">
    select * from Studentinfo where id = #{id}
</select>

/* 测试代码 */
Student id = new Student();
id.setId(1);
Student student = session.selectOne("getUserbyId", id);

/* 分析 */
此时setId()的参数只能为Student对应原始的数据类型数据,即只能为正数,不能传入"1"等
mapper处使用#{}、${}方式结果相同

/* 结果 */
无注入风险

状况二:

/* mapper设置  */
<select id="getUserbyId" resultType="model.Student">
    select * from Studentinfo where id = #{id}
</select>

/* 测试代码 */
Student student = session.selectOne("getUserbyId", 1);

/* 分析 */
mysql特性,selectOne()传入任何数据,都只会取最前面合法的参数去执行语句,无合法数据将返回null

/* 结果 */
无注入风险

状况三:

/* mapper设置  */
<select id="getUserbyId" resultType="model.Student">
    select * from Studentinfo where id = ${id}
</select>

/* 测试代码 */
Student student = session.selectOne("getUserbyId", 1);
selectOne()传入INT,STRING类型数据,都会产生报错提示以下
There is no getter for property named 'id' in 'class java.lang.Integer'

/* 分析 */
${}表示mybatis将传入的对象原样经过get方法获取其值后代入sql语句执行
而如上传入的简单数据类型做为object非对象无get方法,故执行报错

/* 结果 */
无注入风险

状况四:

/* mapper设置  */
<select id="getUserbyId" resultType="model.Student">
    select * from Studentinfo where id = ${id}
</select>

/* 测试代码 */
Map params = new HashMap();
params.put("id", 1);
Student student = session.selectOne("getUserbyId", params);

/* 分析 */
selectOne()传入map对象,${}经过get方式获取id属性值原样代入sql语句执行

/* 结果 */
存在注入风险

/* 利用 */
Map params = new HashMap();
params.put("id", "1 or 1=1 limit 0,2");
Student student = session.selectOne("getUserbyId", params);

/* 修复方案 */
#{}方式处理传入的参数

状况五:

/* mapper设置  */
<select id="getUserbyId" resultType="model.Student">
    select * from Studentinfo order by ${ordername}
</select>

/* 测试代码 */
Map params = new HashMap();
params.put("ordername", "name");
Student student = session.selectOne("getUserbyId", params);

/* 分析 */

/* 结果 */
存在注入风险

/* 利用 */
Map params = new HashMap();
params.put("ordername", "'");
Student student = session.selectOne("getUserbyId", params);

/* 修复方案 */
#{}方式处理传入的参数
select * from Studentinfo order by #{ordername}
sortname方式相同

状况六:

/* mapper设置  */
<select id="getUserbyId" parameterType="map" resultType="model.Student">
    select * from Studentinfo
    <if test="ordername != ''">
        order by
        <if test="ordername == 'name' ">
            name
        </if>
        <if test="ordername == 'id' ">
            id
        </if>
    </if>
</select>

/* 测试代码 */
Map params = new HashMap();
params.put("ordername", "name");
Student student = session.selectOne("getUserbyId", params);

/* 分析 */
传入不存在的odername便可注入

/* 结果 */
存在注入风险

/* 利用 */
Map params = new HashMap();
params.put("ordername", "'");
Student student = session.selectOne("getUserbyId", params);

/* 修复方案 */
方案一:
#{}方式处理传入的参数
select * from Studentinfo order by #{ordername}

方案二:
白名单形式判断传入的ordername是否属于合法
<if test="ordername != '' and ordername in ('name', 'id') ">

状况七:

/* mapper设置  */
<select id="getUserbyId" parameterType="map" resultType="model.Student">
    select * from Studentinfo where name like '%${name}%'
</select>

/* 测试代码 */
Map params = new HashMap();
params.put("name", "he");
Student student = session.selectOne("getUserbyId", params);

/* 分析 */

/* 结果 */
存在注入风险

/* 利用 */
Map params = new HashMap();
params.put("name", "'");
Student student = session.selectOne("getUserbyId", params);

/* 修复方案 */
方案一:
使用concat()函数链接
select * from Studentinfo where name like CONCAT('%',#{name},'%')

方案二:
使用官方推荐的bind()函数完成参数绑定
 <bind name="pattern" value="'%' + name + '%'" />
     select * from Studentinfo where name like #{pattern}

状况八:

/**
* in方式查询相似,可以使用foreach方式构建
* 详见官方文档:http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html
*/

参考连接

http://www.mybatis.org/mybatis-3/zh/index.html
http://blog.csdn.net/kucoll/article/details/51679371 
https://www.google.com.hk
https://www.baidu.com/

最后

此文仅为对mybatis的一些初步认识,稍后再遇到相关项目再更新...

20171022更新

京东安全应急响应中心这篇文章不错,更多深刻分析见
http://mp.weixin.qq.com/s?__biz=MjM5OTk2MTMxOQ==&mid=2727827368&idx=1&sn=765d0835f0069b5145523c31e8229850&mpshare=1&scene=1&srcid=0926iDLkz6CKTO9IUh5fqt3o#rd

漏洞场景

1. 模糊查询like

还以第一节中提到的新闻详情页面为例,按照新闻标题对新闻进行模糊查询,若是考虑安全编码规范问题,其对应的SQL语句以下:

Select * from news where title like ‘%#{title}%’,

但因为这样写程序会报错,研发人员将SQL查询语句修改以下:

Select * from news where title like ‘%${title}%’,

在这种状况下咱们发现程序再也不报错,可是此时产生了SQL语句拼接问题,若是java代码层面没有对用户输入的内容作处理势必会产生SQL注入漏洞。
2. in以后的参数

在对新闻进行同条件多值查询的时候,如当用户输入1001,1002,1003…100N时,若是考虑安全编码规范问题,其对应的SQL语句以下:

Select * from news where id in (#{id}),

但因为这样写程序会报错,研发人员将SQL查询语句修改以下:

Select * from news where id in (${id}),

修改SQL语句以后,程序中止报错,可是却引入了SQL语句拼接的问题,若是研发人员没有对用户输入的内容作过滤,势必会产生SQL注入漏洞。
3. order by以后

当根据发布时间、点击量等信息对新闻进行排序的时候,若是考虑安全编码规范问题,其对应的SQL语句以下:

Select * from news where title =‘京东’ order by #{time} asc,

但因为发布时间time不是用户输入的参数,没法使用预编译。研发人员将SQL查询语句修改以下:

Select * from news where title =‘京东’ order by ${time} asc,

修改以后,程序经过预编译,可是产生了SQL语句拼接问题,极有可能引起SQL注入漏洞。

修复方案

1. 模糊查询like SQL注入修复建议

按照新闻标题对新闻进行模糊查询,可将SQL查询语句设计以下:

select * from news where tile like concat(‘%’,#{title}, ‘%’),

采用预编译机制,避免了SQL语句拼接的问题,从根源上防止了SQL注入漏洞的产生。
2.  in以后的参数SQL注入修复建议

在对新闻进行同条件多值查询的时候,可以使用Mybatis自带循环指令解决SQL语句动态拼接的问题:

select * from news where id in

<foreach collection="ids" item="item" open="("separator="," close=")">#{item} </foreach>
3. order by SQL注入修复建议--在Java层面作映射

预编译机制只能处理查询参数,其余地方还须要研发人员根据具体状况来解决。如前面提到的排序情景: Select * from news where title =‘京东’ order by #{time} asc,这里time不是查询参数,没法使用预编译机制,只能这样拼接:Select * from news where title =‘京东’ order by ${time} asc 。

针对这种状况研发人员能够在java层面作映射来进行解决。如当存在发布时间time和点击量click两种排序选择时,咱们能够限制用户只能输入1和2。当用户输入1时,咱们在代码层面将其映射为time,当用户输入2时,将其映射为click。而当用户输入1和2以外的其余内容时,咱们能够将其转换为默认排序选择time(或者click)。
相关文章
相关标签/搜索