如何防止 SQL 注入?从原理到实战,一篇就够了
SQL 注入是指攻击者通过在用户输入中插入恶意的 SQL 代码,欺骗后端服务器执行非预期的 SQL 命令,从而达到窃取数据、绕过认证、删库跑路等目的。防止 SQL 注入的最佳实践可以归纳为一条铁律:使用预编译 + 输入校验 + 最小权限 + ORM。其中,预编译(参数化查询)是目前公认最有效、最简单的方式。任何外部数据(GET/POST/Cookie/Header)都不能直接拼接到 SQL 中。动
作为一名后端开发,SQL 注入是必须跨过的一道坎。它连续多年位列 OWASP Top 10 榜首,是危害最大的 Web 漏洞之一。本文将带你彻底理解 SQL 注入的原理,并给出 7 条行之有效的防御措施。
一、什么是 SQL 注入?
SQL 注入是指攻击者通过在用户输入中插入恶意的 SQL 代码,欺骗后端服务器执行非预期的 SQL 命令,从而达到窃取数据、绕过认证、删库跑路等目的。
一个经典例子
假设登录接口的 SQL 查询为:
SELECT * FROM users WHERE username = '{$username}' AND password = '{$password}'
攻击者输入:
- username:
admin' -- - password: 任意值
拼接后的 SQL 变成:
SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'xxx'
-- 将后续的密码检查变成了注释,攻击者无需密码即可登录管理员账号。
二、SQL 注入的危害
- 数据泄露:读取数据库中的敏感信息(用户资料、银行卡号)。
- 身份伪造:绕过登录验证,获取管理员权限。
- 数据篡改:修改、删除数据库记录。
- 服务器沦陷:利用
INTO OUTFILE写入 WebShell,进一步控制服务器。
三、如何有效防止 SQL 注入?
记住一个核心原则:永远不要相信用户输入。以下措施应当组合使用。
1. 使用预编译(PreparedStatement)—— 最有效的方法
预编译将 SQL 语句的结构与参数分离,数据库会先编译 SQL 模板,再将参数作为纯数据传递,因此参数中的 SQL 关键字不会被解析。
| 语言 | 正确示例 |
|---|---|
| Java | PreparedStatement stmt = conn.prepareStatement("SELECT * FROM user WHERE id=?");stmt.setInt(1, userId); |
| PHP (PDO) | $stmt = $pdo->prepare("SELECT * FROM user WHERE id = :id");$stmt->execute(['id' => $id]); |
| Python | cursor.execute("SELECT * FROM user WHERE id = %s", (user_id,)) |
| Go | db.QueryRow("SELECT * FROM user WHERE id = ?", userID) |
注意:预编译只能保护 SQL 语句中的值,不能用于表名、列名或 ORDER BY 后的字段名。这些动态标识符需要额外白名单校验。
2. 对输入进行严格的类型校验与转义
当无法使用预编译时(例如需要动态拼接表名、列名),必须:
- 类型校验:如使用
intval()确保 ID 是整型。 - 白名单过滤:例如
$allowedColumns = ['id', 'name']; if (!in_array($column, $allowedColumns)) { exit; } - 转义函数:如 MySQL 的
mysqli_real_escape_string(),但注意转义不能完全防御所有注入,不推荐依赖它。
3. 使用存储过程(谨慎使用)
存储过程同样需要传参,如果内部还是动态拼接 SQL,依然有注入风险。正确做法:使用存储过程配合参数化查询。
4. 最小权限原则
数据库账号应遵循最小必要权限:
- 普通业务账号不应具有
DROP、ALTER、FILE等权限。 - 只授予
SELECT、INSERT、UPDATE、DELETE必要权限。 - 不同应用使用不同账号,隔离风险。
5. 对输出进行编码(纵深防御)
即使有 SQL 注入,如果能将返回的数据进行 HTML 编码,也可以缓解 XSS(跨站脚本)。但这对 SQL 注入本身无直接防御作用,只是减少连锁危害。
6. 使用 ORM 框架
ORM(如 Hibernate、MyBatis、Entity Framework、GORM 等)默认使用参数化查询,大大降低了手工拼接 SQL 的概率。但要注意:
- 避免使用 ORM 提供的原生 SQL 拼接功能(如 MyBatis 的
${})。 - 始终使用参数绑定(如 MyBatis 的
#{})。
7. 部署 Web 应用防火墙(WAF)与定期扫描
- WAF 可以拦截常见 SQL 注入攻击载荷。
- 使用 sqlmap、AppScan 等工具定期扫描漏洞。
- 开启数据库日志,监控异常查询。
四、常见误区
| 误区 | 正确观点 |
|---|---|
| 客户端 JS 校验就够了 | 攻击者可以绕过前端直接发请求,服务端必须校验。 |
| 使用正则过滤就能防注入 | 黑名单很难覆盖所有攻击变种(如编码绕过、内联注释)。预编译才是正解。 |
| 数字类型不需要处理 | 即使 id=1 可直接拼,也建议参数化,因为 1 AND 1=1 也可能是注入点。 |
| 存储过程是安全的 | 存储过程内部若使用动态 SQL 拼接,同样存在注入风险。 |
五、总结
防止 SQL 注入的最佳实践可以归纳为一条铁律:使用预编译 + 输入校验 + 最小权限 + ORM。其中,预编译(参数化查询)是目前公认最有效、最简单的方式。
在实际开发中,请严格遵守:
- 任何外部数据(GET/POST/Cookie/Header)都不能直接拼接到 SQL 中。
- 动态表名、列名必须通过白名单校验。
- 定期进行安全审计和渗透测试。
只有把安全意识融入编码习惯,才能构建出更健壮的应用。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)