1、MyBatis中#{}与${}的区别
在mybatis中#{}可以防止SQL注入而${}不可以。举个例子:
http://www.xx.com/news.jsp?id=1
这里的sql语句中如果使用${},
SELECT title,content FROM news WHERE id = ${id};
执行时,参数id会被直接拼接入sql语句:
SELECT title,content FROM news WHERE id = 1;
如果攻击者提交的参数为
“id=1 and 1=2 UNION SELECT username, password FROM admin”;
拼接的sql语句就变为了:
“SELECT title,content FROM news WHERE id = 1 and 1=2 UNION SELECT username, password FROM admin”;
这条sql的原意就会被改变,导致将管理员数据表中的用户显示在页面title位置,密码显示在页面content位置,达到成功攻击的效果。而如果用#{},
SELECT title,content FROM news WHERE id = #{id};
在用户提交参数之前,sql语句会进行一次预编译,
SELECT title,content FROM news WHERE id = ?;
# 这种方式在最后运行的时候会将 “id=1 and 1=2 UNION SELECT username, password FROM admin” 当成一个整体拼在sql中,大概是如下这种效果
SELECT title,content FROM news WHERE id = ‘id=1 and 1=2 UNION SELECT username, password FROM admin’;
攻击者提交的参数中包含的sql编译字符,不会被带入sql进行编译,只作为参数,不能造成sql注入。而且由于只进行一次预编译,sql的性能也会得到提升。在项目中,大部分SQL语句对参数的处理方式都是用了#{}这种预编译方式。但在模糊查询中,使用#{}会报错。所以使用了${ }方式代替#{},其实在项目中可以使用 concat 的方式,进行参数的拼接。
2、MyBatis框架下易产生SQL注入漏洞场景分析
MyBatis框架下易产生SQL注入漏洞的情况主要分为以下三种:
以按照标题进行模糊查询为例,如果考虑安全编码规范问题,其对应的SQL语句如下:
select * from news where title like ‘%#{title}%’;
但由于这样写程序会报错,所以将SQL查询语句修改如下:
select * from news where title like ‘%${title}%’;
在这种情况下程序不再报错,但是此时产生了SQL语句拼接问题,如果java代码层面没有对用户输入的内容做处理势必会产生SQL注入漏洞。
在进行同条件多值查询的时候,如当用户输入1001,1002,1003…100N时,如果考虑安全编码规范问题,其对应的SQL语句如下:
select * from news where id in (#{id})
但由于这样写程序会报错,研发人员将SQL查询语句修改如下:
select * from news where id in (${id})
修改SQL语句之后,程序停止报错,但是却引入了SQL语句拼接的问题,如果没有对用户输入的内容做过滤,势必会产生SQL注入漏洞。
当根据发布时间、点击量等信息进行排序的时候,如果考虑安全编码规范问题,其对应的SQL语句如下:
select * from news where title =‘test’ order by #{time} asc
但由于这样写程序会报错,将SQL查询语句修改如下:
select * from news where title =‘test’ order by ${time} asc
修改之后,程序通过预编译,但是产生了SQL语句拼接问题,极有可能引发SQL注入漏洞。
3、Mybatis框架下SQL注入漏洞修复建议
按照标题进行模糊查询,可将SQL查询语句设计如下:
select * from news where tile like concat(‘%’,#{title}, ‘%’);
采用预编译机制,避免了SQL语句拼接的问题,从根源上防止了SQL注入漏洞的产生。
在对进行同条件多值查询的时候,可使用Mybatis自带循环指令解决SQL语句动态拼接的问题:
select * from news where id in
< foreach collection=”ids” item=”item” open=”(“ separator=”,” close=”)“>
#{item}
< /foreach>
预编译机制只能处理查询参数,其他地方还需要研发人员根据具体情况来解决。如前面提到的排序情景:
select * from news where title =‘test’ order by #{time} asc
这里无法使用预编译机制,只能这样拼接:
select * from news where title =‘title’ order by ${time} asc
针对这种情况可以在java层面做映射来进行解决。如当存在发布时间time和点击量click两种排序选择时,可以限制用户只能输入1和2。当用户输入1时,在代码层面将其映射为time,当用户输入2时,将其映射为click。而当用户输入1和2之外的其他内容时,可以将其转换为默认排序选择time(或者click)。