前言
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 中
