前言
SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编写时的疏忽,通过SQL语句,实现无账号登录,甚至篡改数据库,本文参考网上资料整理。
如何防御SQL注入
「外部数据不可信任」的原则,纵观Web安全领域的各种攻击方式,大多数都是因为开发者违反了这个原则而导致的,所以自然能想到的,就是从变量的检测、过滤、验证下手,确保变量是开发者所预想的。
- 检查变量数据类型和格式:只要是有固定格式的变量,在SQL语句执行前,应该严格按照固定格式去检查,确保变量是我们预想的格式,这样很大程度上可以避免SQL注入攻击。
- 过滤特殊符号:对于无法确定固定格式的变量,一定要进行特殊符号过滤或转义处理。
- 绑定变量,使用预编译语句:MySQL的mysqli驱动提供了预编译语句的支持。实际上,绑定变量使用预编译语句是预防SQL注入的最佳方式,使用预编译的SQL语句语义不会发生改变,在SQL语句中,变量用问号?表示,黑客即使本事再大,也无法改变SQL语句的结构。
SQL预编译
无法使用sql预编译的情况
1、对于关键词order by来说,如果使用预编译处理,参数绑定为String类型,order by 的参数会被单引号包裹,导致无法排序 // 还有like
2、对于拼接列名、表名的sql语句来说,参数绑定后也会用单引号包裹,故也无法使用预编译处理
3、在prepare绑定参数阶段也能够报错注入
4、某些数据库不支持预编译(如sqllite与低版本mysql),可以使用模拟预编译
原理及方法
以mysql数据库为例:通常情况下,在数据库接收到一条普通的SQL语句后,
首先对其进行语义解析,随后对此条SQL语句进行优化并制定执行计划并执行;
当采用预编译操作时,首先将待执行的SQL语句中的参数值用占位符替代。当带着占位符的SQL语句模板被数据库编译、解析后,再通过向占位符绑定参数进行查询操作。
经过预编译操作之后,无论后续向模板传入什么参数,这些参数仅仅被当成字符串进行查询处理,因此杜绝了sql注入的产生
使用预编译四步走:
1:定义预编译的sql语句,其中待填入的参数用 `?` 占位。注意,?无关类型,不需要加分号之类。其具体数据类型在下面setXX()时决定。
2:创建预编译Statement,并把sql语句传入。此时sql语句已与此preparedStatement绑定。所以第4步执行语句时无需再把sql语句作为参数传入execute()。
3:填入具体参数。通过setXX(问号下标,数值)来为sql语句填入具体数据。注意:问号下标从1开始,setXX与数值类型有关,字符串就是setString(index,str).
4:执行预处理对象。主要有
boolean execute() 在此 PreparedStatement 对象中执行 SQL 语句,该语句可以是任何种类的 SQL 语句
ResultSet executeQuery() 在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询生成的 ResultSet 对象
int executeUpdate() 在此 PreparedStatement 对象中执行 SQL 语句,该语句必须是一个 SQL 数据操作语言(Data Manipulation Language,DML)语句,比如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。
示例代码:
String username = "ye";
String password = "ye";
String sql = "select * from user where username = ? and password = ?;";
db.stmt = db.conn.prepareStatement(sql);
db.stmt.setString(1, username);
db.stmt.setString(2, password);
ResultSet rs = db.stmt.executeQuery();
mybatis预编译
mybatis 中使用 sqlMap 进行 sql 查询时,经常需要动态传递参数,使用#{ }
和 ${ }
来进行动态传参
select * from user where name = "test";
select * from user where name = #{name};
或
select * from user where name = '${name}';
但是在动态 SQL 解析阶段,#{ }
和 ${ }
会有不同的表现:
#{ }会解析为一个 JDBC 预编译语句(prepared statement)的参数标记符
eg:
select * from user where name = #{name};
# 解析为:
select * from user where name = ?;
一个 #{ }
被解析为一个参数占位符 ?
${ } 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换
select * from user where name = '${name}';
# 当传递的参数为 "test" 时,上述 sql 的解析为
select * from user where name = "test";
预编译之前的 SQL 语句已经不包含变量 name 了
${ }
的变量的替换阶段是在动态 SQL 解析阶段,而 #{ }
的变量的替换是在 DBMS 中