预处理语句是唯一可靠的sql注入防护方式,必须禁用pdo模拟模式(设置pdo::attr_emulate_prepares => false),严格使用占位符绑定参数,表名列名等非值部分须通过白名单校验。

唯一可靠的方式是用预处理语句,其他方法(如 addslashes()、mysql_real_escape_string())要么已废弃,要么存在宽字节绕过、数字字段失效等致命缺陷。
PDO预处理必须禁用模拟模式
默认情况下 PDO::ATTR_EMULATE_PREPARES 是开启的,这意味着 PDO 自己拼接 SQL 字符串,而不是交给 MySQL 服务端真正预编译——此时攻击者仍可能绕过防护。
- 务必在创建
PDO实例时显式关闭:PDO::ATTR_EMULATE_PREPARES => false - 同时建议开启异常模式:
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,便于及时发现绑定错误 - 如果连接后执行
$pdo->getAttribute(PDO::ATTR_EMULATE_PREPARES)返回true,说明没生效
mysqli bind_param 的类型和引用陷阱
mysqli_stmt::bind_param() 不是传值,而是传引用;类型字符串顺序、数量、变量可变性都必须严格匹配,否则直接报错或静默失败。
- 类型字母必须准确:
i(int)、s(string)、d(double)、b(blob) - 所有变量必须加
&引用符号,例如$stmt->bind_param("si", $id, $name)中的$id和$name都得是变量,不能是表达式或函数调用结果 - 常见错误:
bind_param("s", $_POST['user'])会报Number of variables doesn't match number of parameters,因为$_POST['user']不是可引用的变量
非值上下文(表名、字段、ORDER BY)不能参数化
预处理语句只保护“值”,对 SQL 结构部分(如表名、列名、排序方向、GROUP BY 子句)完全无效。这些地方拼接即高危,必须白名单校验。
- 错误示例:
"SELECT * FROM {$_GET['table']} WHERE id = ?"—— 表名无法用占位符 - 正确做法:限定允许范围,用
in_array()判断,例如$table = in_array($_GET['table'], ['users', 'posts', 'comments']) ? $_GET['table'] : 'users'; - 排序方向同理:
$order = ($_GET['sort'] ?? 'ASC') === 'DESC' ? 'DESC' : 'ASC';,再拼进 SQL
数字参数别信过滤函数,必须走占位符
intval() 或 filter_var($x, FILTER_VALIDATE_INT) 只能作为辅助校验,不能替代预处理。一旦漏掉或写错位置,比如拼在 SQL 字符串里,就前功尽弃。
- 危险写法:
"WHERE id = " . intval($_GET['id'])—— 数字不加引号,mysqli_real_escape_string()完全无效,且intval()溢出或负数可能被忽略 - 安全写法:统一用
?占位符 +PDO::PARAM_INT或bind_param("i", $id) - 特别注意:
$_GET['id']是字符串,但绑定为整型后,数据库收到的就是纯数值,不会触发任何字符串解析逻辑
最易被忽略的是:表名、排序字段、LIMIT 参数这些“看着像值”的东西,其实根本不在预处理保护范围内;而开发者常因图省事硬编码白名单逻辑,却忘了校验失败后的默认值是否安全。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











