预处理语句真正防注入需同时满足三个硬性条件:所有外部输入必须进入占位符、禁用模拟预处理(pdo::attr_emulate_prepares=false)、绑定时严格指定参数类型;缺一不可,否则仍存在注入风险。

预处理语句不是“调了 prepare() 就安全”,它只在满足三个硬性条件时才真正防注入:SQL 中所有外部输入都进占位符、禁用模拟预处理、绑定时指定类型。漏掉任一环,mysqli_prepare() 或 PDO::prepare() 都形同虚设。
为什么 mysqli_query() 拼接字符串一定出事
用户输入直接混进 SQL 字符串里,数据库根本分不清哪是逻辑、哪是数据。比如 $_GET['id'] = "1 OR 1=1",拼成 "SELECT * FROM users WHERE id = 1 OR 1=1",整张表就查出来了。
加单引号也不行:"id = '" . $_GET['id'] . "'" 遇到 ' OR '1'='1 还是崩;mysqli_real_escape_string() 依赖字符集,宽字节(如 GBK)下可被绕过;数字字段不加引号,转义函数压根不生效——id = "1 OR 1=1" 直接执行。
mysql_* 函数 PHP 8.0+ 已彻底移除,连连接都建不了。
PDO::prepare() 怎么写才真安全
关键不是调了 prepare(),而是是否禁用了模拟预处理、是否绑定时指定了类型、是否所有外部输入都进了占位符。
- 连接时必须关掉模拟:
PDO::ATTR_EMULATE_PREPARES => false - 错误写法:
$pdo->prepare("SELECT * FROM user WHERE name = '{$_POST['name']}'")—— 这仍是拼接,prepare白调 - 正确绑定方式:用
bindValue()显式传值和类型,比如$stmt->bindValue(1, $_POST['status'], PDO::PARAM_STR) - 命名占位符更清晰:
WHERE status = :status,绑定时用bindValue(':status', $val) -
execute()支持数组传参,但必须确保数组元素和占位符一一对应、类型合理
mysqli_prepare() 绑定参数的硬性规则
bind_param() 不是“把变量塞进去”那么简单,它对变量存在形式、类型标识、引用关系都有强制要求。
- 第一个参数是类型字符串,比如
"si"表示“字符串+整数”,顺序、数量、字母必须和后续变量完全匹配 - 后续变量必须是真实存在的变量(不能是
$_GET['id'] + 1这种表达式),且要传引用(&$email) - 常见报错
Number of variables doesn't match number of parameters就是因为类型字符串长度和变量数对不上 - 整数必须用
i,字符串用s,错一个可能导致值变 0 或空字符串,且无提示 - 绑定前必须先给变量赋值,不能在
bind_param()调用时现场计算
哪些地方预处理也救不了你
预处理只保「值」的安全,不保「结构」。凡是不能放在 VALUES、WHERE、SET 右侧的位置,都不能用占位符。
-
SELECT * FROM ?—— 报错,表名不能参数化 -
ORDER BY ?—— 报错,字段名和排序方向不能参数化 -
LIMIT ?在部分 MySQL 版本中支持,但必须强转为整型:(int)$_GET['limit'] - 表名、字段名、排序方向(
ASC/DESC)必须白名单校验:in_array($sort, ['id', 'title'], true),注意第三个参数true启用严格类型检查
最容易被忽略的是:预处理本身不校验输入类型。比如把字符串 ID 绑定为 PDO::PARAM_INT,值会被截断或转成 0;而把整数绑定为 PDO::PARAM_STR,可能让索引失效。绑定前必须自己做类型判断或过滤。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











