唯一有效防御sql注入的方式是使用pdo或mysqli预处理语句,禁用模拟预处理、显式指定charset=utf8mb4字符集、严格绑定参数类型;其他方法如addslashes()已过时且不安全。

直接用 PDO 或 MySQLi 的预处理语句,禁用模拟预处理、显式指定字符集、参数类型严格绑定——这是当前唯一被广泛验证有效的防御方式。其他方法(如 addslashes()、mysql_real_escape_string())已过时或存在绕过风险,不建议单独使用。
PDO连接必须加 charset=utf8mb4 且禁用模拟预处理
很多线上漏洞不是因为没写预处理,而是 DSN 缺少字符集声明,或默认开启了模拟预处理(PDO::ATTR_EMULATE_PREPARES => true),导致底层退化为字符串拼接。
-
PDO::ATTR_EMULATE_PREPARES => false必须显式设置,否则在低版本 MySQL 或某些配置下会失效 -
charset=utf8mb4要写在 DSN 里(如"mysql:host=localhost;dbname=test;charset=utf8mb4"),不能靠后续执行SET NAMES - 错误示例:
new PDO("mysql:host=localhost;dbname=test", $u, $p)—— 没字符集、没关模拟,等于裸奔
MySQLi 中 bind_param() 的类型和引用必须严格匹配
MySQLi 不像 PDO 那样能自动推断类型,bind_param() 的第一个参数是类型字符串,后面每个变量都必须是引用(&$var),否则报错或静默失败。
- 类型字母必须准确:
i(整型)、s(字符串)、d(浮点)、b(blob) - 变量传参必须带
&,例如$stmt->bind_param("is", $id, $name)中的$id和$name得提前定义好,且是变量而非表达式 - 常见错误:
bind_param("s", $_POST['email'])—— 直接传超全局数组值,PHP 报Number of variables doesn't match number of parameters
表名、字段名、ORDER BY 等动态部分无法参数化,必须白名单校验
占位符(? 或 :name)只能用于数据值,不能用于 SQL 结构。试图用 prepare("SELECT * FROM ?") 会直接报错。
-
ORDER BY字段:用in_array($sort, ['id', 'name', 'created_at'], true)校验后拼入 SQL - 搜索关键词含
LIKE:通配符(%)必须在 PHP 层添加,再整体作为参数绑定,例如$search = "%{$_POST['q']}%"; $stmt->execute([$search]) - 枚举类参数(如状态码):优先转为整型并限定范围,
$status = (int)$_GET['status']; if ($status 3) die('invalid');
输入验证不是可选动作,而是前置拦截点
预处理防注入,但不防业务逻辑错误。比如用户传来一个 200 位的邮箱,PDO 会照单全收并存进数据库——直到某天字段溢出或索引失效。
- 数字类:用
filter_var($_GET['id'], FILTER_VALIDATE_INT)或强转(int)后判非负 - 邮箱:用
filter_var($email, FILTER_VALIDATE_EMAIL),别只靠前端正则 - 用户名/路径等标识符:用白名单正则,例如
preg_match('/^[a-z0-9_]{3,16}$/i', $username) - 注意:验证要在
prepare()之前做,避免无效数据进入查询流程
真正容易被忽略的是字符集与模拟预处理的组合失效,以及把白名单校验当成“锦上添花”——它们一旦漏掉,预处理就形同虚设。安全不是某个函数调对了就行,而是一整条链路上每个环节都卡死。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











