既然我们学会了如何提交恶意的注入语句,那么我们到底应该如何去防御注入呢?通常情况下我们可以使用以下方式来防御SQL注入攻击:
- 转义用户请求的参数值中的
'(单引号)、"(双引号)。 - 限制用户传入的数据类型,如预期传入的是数字,那么使用:
Integer.parseInt()/Long.parseLong等转换成整型。 - 使用
PreparedStatement对象提供的SQL语句预编译。
切记只过滤'(单引号)或"(双引号)并不能有效的防止整型注入,但是可以有效的防御字符型注入。解决注入的根本手段应该使用参数预编译的方式。
PreparedStatement SQL预编译查询
将上面存在注入的Java代码改为?(问号)占位的方式即可实现SQL预编译查询。
示例代码片段:
// 获取用户传入的用户IDString id = request.getParameter("id");// 定义最终执行的SQL语句,这里会将用户从请求中传入的host字符串拼接到最终的SQL// 语句当中,从而导致了SQL注入漏洞。String sql = "select id, username, email from sys_user where id =? ";// 创建预编译对象PreparedStatement pstt = connection.prepareStatement(sql);// 设置预编译查询的第一个参数值pstt.setObject(1, id);// 执行SQL语句并获取返回结果对象ResultSet rs = pstt.executeQuery();
需要特别注意的是并不是使用PreparedStatement来执行SQL语句就没有注入漏洞,而是将用户传入部分使用?(问号)占位符表示并使用PreparedStatement预编译SQL语句才能够防止注入!
JDBC预编译
可能很多人都会有一个疑问:JDBC中使用PreparedStatement对象的SQL语句究竟是如何实现预编译的?接下来我们将会以Mysql驱动包为例,深入学习JDBC预编译实现。JDBC预编译查询分为客户端预编译和服务器端预编译,对应的URL配置项是:useServerPrepStmts,当useServerPrepStmts为false时使用客户端(驱动包内完成SQL转义)预编译,useServerPrepStmts为true时使用数据库服务器端预编译。
数据库服务器端预编译
JDBC URL配置示例:
jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false&useServerPrepStmts=true
代码片段:
String sql = "select host,user from mysql.user where user = ? ";PreparedStatement pstt = connection.prepareStatement(sql);pstt.setObject(1, user);
使用JDBC的PreparedStatement查询数据包如下:
客户端预编译
JDBC URL配置示例:
jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false&useServerPrepStmts=false
代码片段:
String sql = "select host,user from mysql.user where user = ? ";PreparedStatement pstt = connection.prepareStatement(sql);pstt.setObject(1, user);
使用JDBC的PreparedStatement查询数据包如下:
对应的Mysql客户端驱动包预编译代码在com.mysql.jdbc.PreparedStatement类的setString方法,如下:
预编译前的值为root',预编译后的值为'root\'',和我们通过WireShark抓包的结果一致。
Mysql预编译
Mysql默认提供了预编译命令:prepare,使用prepare命令可以在Mysql数据库服务端实现预编译查询。prepare查询示例:
prepare stmt from 'select host,user from mysql.user where user = ?';set @username='root';execute stmt using @username;
查询结果如下:
mysql> prepare stmt from 'select host,user from mysql.user where user = ?';Query OK, 0 rows affected (0.00 sec)Statement preparedmysql> set @username='root';Query OK, 0 rows affected (0.00 sec)mysql> execute stmt using @username;+-----------+------+| host | user |+-----------+------+| localhost | root |+-----------+------+1 row in set (0.00 sec)
