SQL注入防范
强类型语言防数字型注入
JAVA、C#等强类型语言几乎可以完全忽略数字型注入,程序在接收ID参数后,做了一次数据类型转换,如果ID参数接收的数据是字符串,那么转换时将会发生Exception
1 | //接收ID参数并转换为int类型 |
但是像PHP、ASP这样的弱类型语言没有强制要求数据类型,会根据参数自动推导出数据类型。这没有对id变量机型数据类型转换,如果出现字符串类型会直接带入数据库查询,从而导致SQL注入
1 | $id = $_GET['id']; |
针对这个问题,也可以使用一些函数来进行数据类型判断,is_numeric()、ctype_digit()来对是否为数据进行判断
特殊字符转义
判断输入字符串中是否存在敏感字符,如果存在进行转义,MySQL使用\进行转义
当出现单引号时,会变成\',从而让单引号无法有效
预编译语句+参数化查询
预编译
预编译 指的是 SQL 语句的结构在数据库端被提前编译和优化,而参数值在运行时才传入。可以把这想象成一个 “填空题模板”
1
2 -- 这是一个预编译的"模板"
SELECT * FROM users WHERE username = ? AND password = ?传统 Statement vs PreparedStatement
1. 传统 Statement(字符串拼接)
1
2
3
4
5
6
7 String username = "admin";
String password = "123456";
// 直接拼接SQL字符串
String sql = "SELECT * FROM users HERE username = '" + username + "' AND password = '" + password + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);执行过程:
- 拼接完整SQL:
SELECT * FROM users WHERE username = 'admin' AND password = '123456'- 每次执行都要:语法分析 → 语义分析 → 优化 → 生成执行计划 → 执行
- 重复劳动:即使结构相同,只是参数不同,也要重新编译
2. PreparedStatement(预编译)
1
2
3
4
5
6
7
8
9
10
11 String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement ps = conn.prepareStatement(sql); // ⭐预编译发生在这里⭐
// 后续多次执行
ps.setString(1, "admin");
ps.setString(2, "123456");
ResultSet rs1 = ps.executeQuery();
ps.setString(1, "user1");
ps.setString(2, "password1");
ResultSet rs2 = ps.executeQuery(); // 重用编译结果执行过程:
prepareStatement()时:数据库收到SELECT * FROM users WHERE username = ? AND password = ?
- 进行语法分析、语义检查、优化、生成执行计划
- 将编译结果缓存起来
executeQuery()时:只需要传入参数值,直接使用缓存的执行计划
JAVA、C#等语言都提供了预编译语句
JAVA中提供了三个接口与数据库交互,分别是Statement、PreparedStatement、CallableStatement
Statement用于执行静态SQL语句,并返回所生成结果的对象。PreparedStatement为Statement的子类,表示预编译SQL语句的对象。CallableStatement为PreparedStatement的子类,用于执行SQL存储过程
安全代码
1 | int id = Integer.parseInt(request.getParameter("id")); |
- 使用参数化查询,用户输入的
id被作为参数传递,而不是直接拼接到 SQL 中,即使攻击者传入1 OR 1=1,也会在Integer.parseInt()处被拦截 - 通过
setInt()方法确保参数类型安全,数据库知道这是一个整数类型,不会当作 SQL 命令解析
总结:不要使用SQL动态拼接语句,如果使用拼接语句,即使使用PreparedStatement也没用
错误例子
1 | String id = request.getParameter("id"); |
框架技术
存在一种专门与数据库打交道的框架,称为持久化框架,有代表性的Hibernate、MyBatis、JORM等
Hibernate是一个开放源代码的ORM(对象关系映射)框架。可以使程序员使用面向对象编程思维操作数据库
Hibernate自定义一种HQL语言,是一种面向对象的数据查询语句
**但凡涉及到从数据库查询数据的情况,尽量避免出现字符串动态拼接**,最好使用参数名称或者位置绑定的方式
(1)代码位置绑定
1 | int id = Integer.parseInt(request.getParameter("id")); |
- Session 是 Hibernate 的核心接口,相当于 JDBC 的 Connection
?:参数占位符
(2)使用参数名称
1 | int id = Integer.parseInt(request.getParameter("id")); |
存储过程
各数据库的存储过程安全用法
1. MySQL
1 | DELIMITER // |
Java 调用:
1 | String sql = "{call GetUserSafe(?)}"; |
2. Oracle
1 | CREATE OR REPLACE PROCEDURE GetEmployeeSafe( |
3. SQL Server
1 | CREATE PROCEDURE GetOrderDetailsSafe |
注意:依旧是不要出现动态拼接的sql语句