MySQL注入风险源于字符串拼接,必须用PDO/mysqli预处理分离SQL与数据;ORDER BY、LIMIT等动态部分需白名单或强转校验,禁用mysql_*函数。

为什么 mysql_query() 拼接字符串必然出事
因为用户输入直接进 SQL 字符串,数据库根本分不清哪部分是逻辑、哪部分是数据。哪怕加了 mysql_real_escape_string(),在多字节编码(如 GBK)或宽字符截断场景下,照样能绕过——这不是补丁能修好的设计缺陷。
- 别信“我只让数字进来”:前端校验可被绕过,后端没验证就等于没设防
-
mysql_*函数在 PHP 7.0+ 已被彻底移除,继续用等于主动放弃安全更新和维护支持 - 哪怕只是
SELECT * FROM user WHERE id = '1 OR 1=1'这种低级写法,也足够拖垮整张表或泄露全部数据
PHP 中必须用 PDO 或 mysqli 的预处理
预编译语句把 SQL 结构和数据彻底分离:先发带占位符的模板给 MySQL 编译,再单独传参数。MySQL 内部根本不会把参数当 SQL 解析,自然没法注入。
- 用
PDO时,prepare()+execute()是标配,bindValue()或bindParam()控制类型更稳 -
mysqli要用prepare()+bind_param(),注意参数类型字母(s字符串、i整数)必须严格匹配 - 别用
exec()或query()执行含变量的 SQL,哪怕变量来自intval()也不保险——类型转换可能失败,且无法覆盖字符串场景
示例(PDO):
$stmt = $pdo->prepare("SELECT * FROM users WHERE status = ? AND role = ?");
$stmt->execute(['active', 'admin']);
哪些地方最容易漏掉预处理
不是只有 WHERE 条件才危险。任何用户可控的字段名、表名、排序方向、LIMIT 偏移量,只要拼进 SQL 字符串,就是突破口。
-
ORDER BY后的字段名不能参数化,得白名单校验:in_array($sort, ['name', 'created_at'], true) -
LIMIT的数字必须用intval()强转,且限制上限(如最大 100),因为bind_param('i')对负数或超大值不报错但行为异常 - 动态表名/列名无法用占位符,只能靠正则过滤(如
/^[a-zA-Z_][a-zA-Z0-9_]*$/)或查数据字典确认存在
兼容性与性能其实不是借口
有人嫌改预处理麻烦,或担心旧项目迁移成本高。但现实是:不改,下次审计或上线就被打穿;改了,性能反而更稳——预编译语句会被 MySQL 缓存执行计划,重复查询更快。
-
PDO默认关闭模拟预处理(PDO::ATTR_EMULATE_PREPARES => false),确保真走 MySQL 预编译,避免客户端层绕过 - 老项目用
mysqli的过程式写法也能预处理,不需要立刻重构成面向对象 - ORM 如 Laravel Eloquent、ThinkPHP QueryBuilder 默认全走预处理,但手写原生 SQL 时仍要自己把关
真正卡住人的,往往是那些没被当回事的 ORDER BY 和 LIMIT 参数——它们不像 WHERE 那么显眼,却一样致命。


















