唯一靠谱的防注入路径是mysqli_prepare()+mysqli_stmt_bind_param(),因其将sql结构与用户输入彻底分离,参数值永不参与语法解析;其他如字符串拼接、mysql_real_escape_string()等均无法根本阻断输入被当作代码执行的风险。

直接用 mysqli_prepare() + mysqli_stmt_bind_param() 是唯一靠谱的防注入路径,其他任何字符串拼接、转义、过滤都算不上防御——只是拖延漏洞暴露时间。
为什么 mysqli_query() 拼字符串一定会出事
因为数据库引擎看到的是完整语句,用户输入一旦没被隔离,就自动获得“代码身份”。比如 id = $_GET['id'],传入 1 OR 1=1,拼出来就是 SELECT * FROM user WHERE id = 1 OR 1=1,全表返回。
常见错误包括:
- 只对字符串字段用
mysqli_real_escape_string(),但数字字段不加引号,转义完全无效 - 忘了调用
mysqli_set_charset($conn, 'utf8mb4'),宽字节注入可绕过转义 - 把变量拼进 SQL 字符串里再传给
mysqli_prepare(),比如"WHERE name = '{$_POST['name']}'",prepare 根本没起作用
mysqli_prepare() 的正确调用链
必须按顺序走完三步:准备 → 绑定 → 执行。缺一不可,且绑定必须在执行前完成。
关键点:
-
mysqli_prepare()返回的是mysqli_stmt对象,不是结果集 -
mysqli_stmt_bind_param()第一个参数是类型字符串,如"si",长度和后续变量个数必须严格一致 - 绑定的变量必须是真实存在的变量(不能是表达式或函数返回值),且需提前赋值
- 占位符
?不能加引号,也不能用于字段名、表名、ORDER BY或GROUP BY后的内容
示例:
$stmt = mysqli_prepare($conn, "SELECT * FROM users WHERE email = ? AND status = ?"); mysqli_stmt_bind_param($stmt, "si", $email, $status); $email = $_POST['email'] ?? ''; $status = (int)($_POST['status'] ?? 1); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt);
类型标识写错会怎样
"s"(字符串)、"i"(整数)、"d"(浮点)、"b"(BLOB)必须和变量实际类型匹配。错一个就会触发 Number of variables doesn't match number of parameters in prepared statement 错误,或者更危险的隐式截断/转换。
典型陷阱:
- 用
"s"绑定一个未初始化的空变量,可能被当成空字符串插入,掩盖逻辑缺陷 - 用
"i"绑定含非数字字符的字符串(如"123abc"),PHP 会静默转成123,丢失校验意图 - 整数字段却用
"s",虽能执行,但可能触发 MySQL 的隐式类型转换,某些边界 case 下仍可绕过
动态表名或排序方向怎么办
预处理语句不支持绑定表名、字段名、ASC/DESC。这时候不能妥协,必须用白名单硬校验。
例如:
- 表名只能从
['users', 'posts', 'comments']中取 - 排序字段限定为
['created_at', 'title', 'score'] -
ORDER BY方向只接受$_GET['sort'] === 'DESC' ? 'DESC' : 'ASC'
拼接前先做 in_array() 判断,不匹配就拒绝请求。别试图用 addslashes() 或正则“清理”字段名——那等于没防。
最常被忽略的一点:预处理防的是“值”,不是“结构”。只要 SQL 模板本身混入了未校验的用户输入,哪怕用了 prepare(),照样崩。安全不在函数名里,而在你控制数据流向的每一步是否真正隔离。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











