Mybatis如何防止SQL注入
什么是SQL注入
sql注入是一种代码注入技术,将恶意的sql插入到被执行的字段中,以不正当的手段多数据库信息进行操作。
在项目开发当中使用的用户一般都是以root用户进行使用而没有
例如
一条普通的查询SQL
select name from user where id = 1;
SQL注入的SQL
select name from user where id = 1;drop table user;
MySQL防止SQL注入
MySQL中的实现方案
预编译 - 使用占位符
Connection conn = getConn();//获得连接
String sql = "select name from user where id= ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userId);
ResultSet rs=pstmt.executeUpdate();
......
? 代表的就是占位符
实现原理:
为什么上面的代码就不存在SQL注入了呢?因为使用了预编译语句,预编译语句在执行时会把"select name from user where id= ?"语句事先编译好,这样当执行时仅仅需要用传入的参数替换掉占位符即可。<u>也就是不会传入drop table user</u> ,只保证传入参数而不传入SQL。
存储过程
存储过程(Stored Procedure)是一组完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过调用存储过程并给定参数(如果该存储过程带有参数)就可以执行它,也可以避免SQL注入攻击
Connection conn = getConn();
// 调用存储过程
stmt = conn.prepareCall("{call name_from_user(?,?)}");
stmt.setInt(1,2);
stmt.registerOutParameter(2, Types.VARCHAR);
stmt.execute();
String name= stmt.getString(2);
存储过程的实现
use user;
delimiter //
create procedure name_from_user(in user_id int,out user_name varchar(20))
begin
select name into user_name from user where id=user_id;
end
//
delimiter ;
字符处理
使用后端做字符处理(PS:如果是使用前端做处理的话,黑客可以越过前端传入参数);
利用字符串处理传入的参数
权限设置
上述的三种处理办法实质上都是对参数进行处理,权限设置的话就是对用户设置最小权限粒度。
创建用户 - create user
-
修改用户权限 - GRANT privileges
常用的权限
- 表数据: select, update, delete, insert
- 表结构: create, alert, drop
- 外键: references
- 创建临时表: create temporary tables
- 操作索引: index
- 视图: create view, show view
- 存储过程: create routine, alert routine, execute
- 所有权限: all
在连接数据库的时候不使用root用户,而是创建其他的用户,根据业务来赋予不同的权限,但是对于DDL不对其他用户进行赋予操作。
MyBatis如何实现的SQL注入
<select id="getNameByUserId" resultType="String">
SELECT name FROM user where id = #{userId}
</select>
对应的java文件为:
public interface UserMapper{
String getNameByUserId(@Param("userId") String userId);
}
可以看到输入的参数是String类型的userId,当我们传入userId="34;drop table user;
"后,打印的语句(Mybatis日志)是这样的:
select name from user where id = ?
不管输入何种userID,他的sql语句都是这样的。这就得益于mybatis在底层实现时使用预编译语句。数据库在执行该语句时,直接使用预编译的语句,然后用传入的userId替换占位符?就去运行了。不存在先替换占位符?再进行编译的过程,因此SQL注入也就没有了生存的余地了。
那么mybatis是如何做到sql预编译的呢?其实框架底层使用的正是PreparedStatement类。PreparedStaement类不但能够避免SQL注入,因为已经预编译,当N次执行同一条sql语句时,节约了(N-1)次的编译时间,从而能够提高效率。
如果将上面的语句改成:
<select id="getNameByUserId" resultType="String">
SELECT name FROM user where id = ${userId}
</select>
当我们输入userId="34;drop table user;
"后,打印的语句是这样的:
select name from user where id = 34;drop table user;
此时,mybatis没有使用预编译语句,它会先进行字符串拼接再执行编译,这个过程正是SQL注入生效的过程。
因此在编写mybatis的映射语句时,尽量采用“#{xxx}”这样的格式。若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止sql注入攻击。