SQL注入攻击中,非法删除数据是极具破坏性的一种场景,攻击者通过在输入参数中嵌入删除相关的SQL片段,篡改原本的SQL逻辑,进而删除数据库中的有效数据。要防范这类风险,使用预处理语句绑定参数是行业公认的有效方案。
SQL注入导致非法删除的原理
传统的SQL拼接方式会将用户输入的内容直接拼接到SQL语句中,如果输入内容包含恶意的SQL片段,就会改变原本的SQL逻辑。比如原本的删除语句是根据用户ID删除对应数据,恶意输入可以让语句变成删除全表数据。
以下是存在漏洞的拼接SQL代码示例,使用PHP和MySQLi实现:
<?php
$servername = "localhost";
$username = "root";
$password = "123456";
$dbname = "test_db";
// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);
// 检查连接
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
// 获取用户输入的用户ID,未做任何过滤
$user_id = $_GET['id'];
// 直接拼接SQL语句,存在注入风险
$sql = "DELETE FROM user_table WHERE id = " . $user_id;
if ($conn->query($sql) === TRUE) {
echo "删除成功";
} else {
echo "删除失败: " . $conn->error;
}
$conn->close();
?>
如果攻击者在URL中输入id=1 OR 1=1,拼接后的SQL会变成DELETE FROM user_table WHERE id = 1 OR 1=1,条件永远成立,会删除user_table表中的所有数据,造成严重的业务损失。
预处理语句绑定参数的防护机制
预处理语句的核心是将SQL语句的结构和数据参数分开处理,数据库会先解析SQL语句的结构,之后再将参数的值传入执行,参数内容不会被当作SQL指令解析,从根源上避免了SQL注入。
预处理语句执行通常分为两步:
- 第一步:prepare阶段,数据库接收SQL模板,解析语法、确定执行计划,此时SQL模板中的参数位置用占位符表示
- 第二步:execute阶段,将实际的参数值传递给数据库,数据库只把这些值当作数据处理,不会解析其中的SQL片段
使用预处理语句绑定参数防范非法删除的示例
同样以PHP和MySQLi为例,使用预处理语句重写上面的删除逻辑,就可以彻底避免注入风险:
<?php
$servername = "localhost";
$username = "root";
$password = "123456";
$dbname = "test_db";
// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);
// 检查连接
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
// 获取用户输入的用户ID
$user_id = $_GET['id'];
// 准备预处理语句,用?作为占位符
$stmt = $conn->prepare("DELETE FROM user_table WHERE id = ?");
// 绑定参数,i表示参数为整数类型
$stmt->bind_param("i", $user_id);
// 执行语句
if ($stmt->execute()) {
echo "删除成功";
} else {
echo "删除失败: " . $stmt->error;
}
// 关闭语句和连接
$stmt->close();
$conn->close();
?>
此时即使用户输入1 OR 1=1,数据库也只会把它当作一个整体的整数参数处理,因为参数类型绑定的是整数,非数字内容会被转换为0,最终执行的删除条件只会是id = 0,不会匹配到任何有效数据,自然无法造成非法删除。
不同场景下的预处理语句使用示例
PDO方式实现预处理删除
如果使用PDO扩展操作数据库,实现方式如下:
<?php
$dsn = "mysql:host=localhost;dbname=test_db;charset=utf8mb4";
$username = "root";
$password = "123456";
try {
$pdo = new PDO($dsn, $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$user_id = $_GET['id'];
// 准备预处理语句
$stmt = $pdo->prepare("DELETE FROM user_table WHERE id = :user_id");
// 绑定参数并执行
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
$stmt->execute();
echo "删除成功";
} catch (PDOException $e) {
echo "操作失败: " . $e->getMessage();
}
?>
Java JDBC方式实现预处理删除
Java开发中可以使用PreparedStatement实现参数绑定:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class DeleteUser {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/test_db?useSSL=false";
String user = "root";
String password = "123456";
Connection conn = null;
PreparedStatement pstmt = null;
try {
// 获取数据库连接
conn = DriverManager.getConnection(url, user, password);
// 准备预处理语句
String sql = "DELETE FROM user_table WHERE id = ?";
pstmt = conn.prepareStatement(sql);
// 假设从请求中获取的用户ID是1,实际场景中从输入参数获取
int userId = 1;
// 绑定参数,设置第一个占位符的值为用户ID
pstmt.setInt(1, userId);
// 执行删除
int rows = pstmt.executeUpdate();
System.out.println("删除了" + rows + "条数据");
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
注意事项
使用预处理语句绑定参数时,需要注意以下几点才能保证防护效果:
- 必须保证SQL模板是固定的,不能将用户输入的内容拼接到SQL模板中,只能将用户输入作为绑定参数传入
- 合理指定参数类型,比如整数类型的参数就绑定为整数类型,避免类型转换带来的风险
- 预处理语句只是防范SQL注入的手段之一,还需要配合输入校验、最小权限原则等安全措施,构建多层防护体系
预处理语句绑定参数是防范SQL注入导致非法删除的有效方案,只要正确使用,就可以从根源上避免恶意SQL拼接带来的风险,是开发过程中必须遵循的安全规范。