什么是 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)。