SQL注入是指攻击者通过在应用程序的输入参数中插入恶意的SQL代码片段,当应用程序未对输入做正确处理就拼接SQL语句执行时,恶意代码会被数据库执行,进而导致数据泄露、篡改或删除等安全问题。防护SQL注入需要从代码编写、数据库配置、权限管理等多个层面共同入手。

SQL注入的典型场景
最常见的注入场景是直接将用户输入拼接到SQL语句中,例如以下存在漏洞的PHP代码:
<?php $username = $_GET['username']; // 直接拼接用户输入到SQL语句,存在注入风险 $sql = "SELECT * FROM users WHERE username = '$username'"; $result = mysqli_query($conn, $sql); ?>
如果攻击者传入的username参数为admin' OR '1'='1,拼接后的SQL语句会变成SELECT * FROM users WHERE username = 'admin' OR '1'='1',此时会返回所有用户数据,造成信息泄露。
核心防护方法
1. 使用参数化查询(预编译语句)
参数化查询是最有效的SQL注入防护手段,它将SQL语句的结构和参数分开传递,数据库会先编译SQL结构,再将参数作为纯数据处理,不会把参数解析为SQL代码。不同语言的实现方式如下:
Java示例(JDBC)
import java.sql.*;
public class SafeQuery {
public static void main(String[] args) {
String username = "admin";
String sql = "SELECT * FROM users WHERE username = ?";
try (Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "password");
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 设置参数,参数会被当作纯数据处理
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("username"));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Python示例(PyMySQL)
import pymysql
conn = pymysql.connect(host='127.0.0.1', user='root', password='password', database='test')
cursor = conn.cursor()
username = "admin"
# 使用占位符传递参数,避免拼接
sql = "SELECT * FROM users WHERE username = %s"
cursor.execute(sql, (username,))
result = cursor.fetchall()
for row in result:
print(row)
conn.close()
2. 严格的输入验证
对所有用户输入进行类型和格式校验,只允许符合预期的内容通过。例如数字类型的参数只允许输入数字,邮箱参数必须符合邮箱格式,日期参数必须符合日期格式。可以使用正则表达式或框架自带的验证组件实现:
// Node.js中输入验证示例
const express = require('express');
const app = express();
app.get('/user', (req, res) => {
const userId = req.query.userId;
// 验证userId是否为数字
if (!/^d+$/.test(userId)) {
return res.status(400).send('用户ID必须为数字');
}
// 后续安全的查询逻辑
});
3. 最小权限原则
数据库连接的账号不要使用root等最高权限账号,而是根据业务需求分配最小必要权限。例如查询接口使用的账号只授予SELECT权限,写入接口使用的账号只授予INSERT、UPDATE权限,避免注入攻击发生后攻击者获取过高权限破坏更多数据。
4. 避免动态拼接SQL
尽量不要在代码中动态拼接SQL语句,尤其是拼接用户输入的内容。如果必须拼接,需要对特殊字符进行转义,例如将单引号转义为两个单引号,但这种方式容易遗漏场景,优先级低于参数化查询。以下是PHP中转义的示例:
<?php $username = mysqli_real_escape_string($conn, $_GET['username']); $sql = "SELECT * FROM users WHERE username = '$username'"; ?>
5. 关闭数据库错误信息回显
不要将数据库的原始错误信息直接返回给用户,攻击者可以通过错误信息判断注入点、获取数据库结构。应该在代码中捕获异常,返回通用的错误提示,例如“操作失败,请稍后重试”。
防护方法对比
以下是不同防护方法的特点对比:
| 防护方法 | 防护效果 | 实现难度 | 推荐优先级 |
|---|---|---|---|
| 参数化查询 | 高 | 低 | 第一优先级 |
| 输入验证 | 中 | 中 | 第二优先级 |
| 最小权限原则 | 中 | 低 | 第三优先级 |
| 特殊字符转义 | 中 | 中 | 第四优先级 |
常见误区
- 认为前端做了验证就安全:前端验证可以被绕过,所有验证必须在后端实现。
- 只过滤单引号:攻击者可以使用其他方式构造注入语句,例如利用注释符、布尔盲注等,单纯过滤单引号无法完全防护。
- 使用ORM框架就绝对安全:ORM框架如果不当使用,例如拼接原生SQL,仍然可能存在注入风险。
SQL注入prepared_statement参数化查询输入验证修改时间:2026-07-06 19:42:13