Java MyBatis3(7)参数取值以及防止SQL 注入
序言
在mybatis中,参数取值方式有两种:#{ } 和 ${ }
一、#{ }
select * from student where name=#{name}
编译后执行的sql语句:
select * from student where name=?
说明:
#{ }实现的是JDBC 中preparedStatement中的占位符。
#{ }适合sql语句中的参数传值,构建sql语句#{ }是不可以的。
select * from #{tablename} ;
编译后的sql语句为:
select * from ?
这在sql中是不允许的,所以要用${ 拼接}
#{ }试用的场景
1.where语句里的判断:
a=#{a},a>#{a},a in {#{a}},a like #{a}........
2.set语句:
set a=#{a}
3.插入语句中:
values(#{a},.......)
4.其他大部分适合${ }进行拼接
二、${ }
select * from student where name=${name}
编译后执行的sql语句:
select * from student where name=name
说明:
${ }取参方式是简单的字符串拼接,不适合进行参数传值,不然会有sql语句注入的危险。
它更加适合的是构建sql语句。
Mybatis 框架下的 SQL 注入问题及防护方法
1、模糊查询
在模糊查询场景下,考虑安全编码规范,使用 #{}
传入参数:
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO"> select * from user_table where username like '%#{username}%' </select>
在这种情况下使用 #{}
程序会报错:
于是很多安全经验不足的程序员就把 #{}
号改成了 ${}
,如果应用层代码没有对用户输入的内容做处理势必会产生SQL注入漏洞。
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO"> select * from user_table where username like '%${username}%' </select>
因此,安全的写法应当使用 CONCAT 函数连接通配符:
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO"> select * from user_table where username like concat('%',#{username},'%') </select>
2、带有 IN 谓词的查询
在 IN 关键字之后使用 #{}
查询多个参数:
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO"> select * from user_table where username in (#{usernames}) </select>
正常提交查询参数 'zxd','hhh'
,因为预编译机制,系统将我们输入的字符当作了一个字符串,因此查询结果为空,不能满足业务功能需求。
于是很多安全经验不足的程序员就把 #{}
号改成了 ${}
:
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO"> select * from user_table where username in (${usernames}) </select>
因此,此种情况下,安全的做法应当使用 foreach 标签:
<select id="getUserFromList" resultType="user.NewUserDO"> select * from user_table where username in <foreach collection="list" item="username" open="(" separator="," close=")"> #{username} </foreach> </select>
3、带有动态排序功能的查询
动态排序功能,需要在 ORDER BY 之后传入参数,考虑安全编码规范,使用 #{}
传入参数:
<select id="getUserOrder" parameterType="java.lang.String" resultType="user.NewUserDO"> select * from user_table order by #{column} limit 0,1 </select>
提交参数 username
根据用户名字段排序。但因为预编译机制,系统将我们输入的字符当作了一个字符串,根据字符串排序是不生效的,不能满足业务功能需求。(根据用户名字段排序,此时正常应返回 root
用户)
于是很多安全经验不足的程序员就把 #{}
号改成了 ${}
:
<select id="getUserOrder" parameterType="java.lang.String" resultType="user.NewUserDO"> select * from user_table order by ${column} limit 0,1 </select>
攻击者提交参数值 username#
,利用 SQL 注入漏洞,成功查询了所有用户数据。
因此,此种情况下,安全的做法应当在 Java 代码层面来进行解决。可以设置一个字段值的白名单,仅允许用户传入白名单内的字段。
String sort = request.getParameter("sort"); String[] sortWhiteList = {"id", "username", "password"}; if(!Arrays.asList(sortWhiteList).contains(sort)){ sort = "id"; }
或者仅允许用户传入索引值,代码再将索引值映射成对应字段。
String sort = request.getParameter("sort"); switch(sort){ case "1": sort = "id"; break; case "2": sort = "username"; break; case "3": sort = "password"; break; default: sort = "id"; break; }
需要注意的是在 mybatis-generator 自动生成的 SQL 语句中,ORDER BY 使用的也是 ${}
,而 LIKE 和 IN 没有问题。