应使用参数化查询而非 mysql_real_escape_string 或 addslashes,因其彻底分离 SQL 结构与数据,避免宽字节绕过及字符集依赖问题;ORM 中需警惕原生 SQL 片段拼接,JSON/NoSQL 存储不改变 SQL 注入防护本质。

存入数据库前用 mysql_real_escape_string 还是 addslashes?
别用这两个。它们只处理单引号、反斜杠等有限字符,且依赖当前连接的字符集;如果数据库连接用的是 gbk 或 latin1,攻击者可以用宽字节绕过,addslashes 完全无效。mysql_real_escape_string 已在 PHP 7.0 中废弃,且仅适用于旧版 MySQL 扩展(mysql_*),不兼容 mysqli 或 PDO。
真正该做的是:** 用参数化查询(Prepared Statement)替代字符串拼接 **。它把 SQL 结构和数据彻底分离,数据库引擎在解析阶段就确定字段类型和边界,根本不会把用户输入当 SQL 代码执行。
-
PDO示例:$stmt = $pdo->prepare("INSERT INTO users (name) VALUES (?)"); $stmt->execute([$user_input]); -
mysqli示例:$stmt = $mysqli->prepare("SELECT * FROM posts WHERE id = ?"); $stmt->bind_param("i", $id); $stmt->execute(); - 所有占位符(
?或命名参数如:name)都由驱动层原生处理,无需手动转义
为什么不能靠“入库前过滤”防存储型 SQL 注入?
因为过滤逻辑容易滞后、不统一、不可靠。比如你写了个 escape_sql 函数,但同事在另一个模块直接拼接了 "UPDATE logs SET msg = '" . $_POST['msg'] . "'";或者过滤只做了 HTML 实体编码(htmlspecialchars),对 SQL 完全无效;又或者用了 stripslashes 反向清理,反而破坏正常数据。
更关键的是:** 同一段数据可能被用于不同上下文 **——今天插进 MySQL,明天导出到 CSV,后天拼进 shell 命令。靠“入库前一刀切转义”,等于把所有数据都按 SQL 上下文预设,后续其他用途反而要额外反转,极易出错。
- 存储型注入的核心风险不在“存”,而在“取出来再拼 SQL 时没防护”
- 所以防护点必须落在每次查询构造环节,而不是入库那一刻
- 数据库字段类型(如
VARCHAR(255))本身不提供任何注入防护能力
ORM 框架里怎么避免手写 SQL 导致的漏洞?
主流 ORM(Laravel Eloquent、Django ORM、SQLAlchemy)默认使用参数化查询,但陷阱常出现在“原生 SQL 片段”或“动态查询构建”中。比如 Laravel 的 whereRaw()、Django 的 extra() 或 raw()、SQLAlchemy 的 text()。
这些方法一旦拼接用户输入,就等于退回到裸 SQL 风险区。必须显式传参,不能字符串插值。
- ❌ 错误:
->whereRaw("name LIKE '%{$_GET['q']}%'") - ✅ 正确:
->whereRaw("name LIKE ?", ["%{$_GET['q']}%"])(Laravel) - ✅ 正确:
session.execute(text("SELECT * FROM users WHERE name = :name"), {"name": name})(SQLAlchemy) - 注意:即使 ORM 提供了
quote()或escape_string()方法,也别用——它们只是模拟转义,不如参数化可靠
JSON 字段或 NoSQL 存储能绕过 SQL 注入防护吗?
不能。SQL 注入只发生在 SQL 查询执行环节,和你存的是纯文本、JSON 还是 BSON 无关。如果你从 MongoDB 取出一个 JSON 字符串,再把它拼进 MySQL 的 INSERT 语句里,照样会触发注入。
真正的分界线是:** 数据是否经过参数化机制进入查询 **。哪怕你用 Redis 存用户输入,只要之后用 redis.get(……) 拿出来,再塞进 "SELECT * FROM x WHERE id = " . $id,风险就存在。
- NoSQL 也有自己的注入问题(如 MongoDB 的
$ne、$regex操作符滥用),但那属于另一类漏洞,不能误以为“不用 SQL 就安全” - JSON 字段内容若后续被
json_decode()后拼进 SQL,仍需走参数化流程 - 最稳妥的做法:所有外部输入,只要参与 SQL 构造,一律走绑定参数,不管它中间经过多少次存储或格式转换
事情说清了就结束。真正难的不是记住哪个函数该用,而是团队里每个人在写 WHERE 条件、写 ORDER BY 字段、甚至写迁移脚本里的 INSERT 时,都下意识拒绝字符串拼接。