必须用预处理语句(mysqli_prepare()或pdo::prepare())防止sql注入,因其将sql结构与数据彻底分离;字符串拼接、转义函数及模拟预处理均不可靠。

为什么 mysqli_query() 拼接字符串一定会出事
因为用户输入的任何内容,只要没经过隔离处理,都会被数据库当作 SQL 代码执行。比如用户名填 ' OR '1'='1,密码留空,拼出来的语句就可能是:SELECT * FROM users WHERE name = '' OR '1'='1' AND pass = ''——直接绕过验证。
这种写法看着省事,但只要有一个地方漏了 mysqli_real_escape_string() 或忘了加引号,漏洞就开了。而且转义函数对某些编码边界情况(如宽字节注入)并不完全可靠。
- 不要用
mysqli_query()+ 字符串拼接处理用户输入 -
mysqli_real_escape_string()不是银弹,不能替代参数化 - 即使加了单引号包裹,
id = $_GET['id']这种整数场景也得强转或预处理,否则id=1 OR 1=1仍可能生效
必须用 mysqli_prepare() + mysqli_stmt_bind_param()
预处理的本质是把 SQL 结构和数据彻底分开:数据库先编译语句模板,再把参数当纯数据塞进去,根本不会去解析它们是不是“SQL”。这是目前 PHP 原生 MySQL 扩展里最可靠的方式。
注意绑定类型字符必须和变量类型匹配,i(整数)、s(字符串)、d(双精度)、b(blob),错一个会导致绑定失败或值被截断。
```php $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']; mysqli_stmt_execute($stmt); ```
-
?占位符不能加引号,也不支持字段名、表名、排序方向(ORDER BY ?是错的) - 绑定前给变量赋值,不是绑定时传值;变量必须是真实存在的变量(不能是表达式或函数调用)
- 如果要动态表名,只能白名单校验后拼接,绝不可直接插进
prepare()的 SQL 字符串里
PDO 的 prepare() 更灵活但默认不真预处理
PDO 默认开启模拟预处理(PDO::ATTR_EMULATE_PREPARES = true),也就是 PHP 自己做占位符替换,再发完整 SQL 给 MySQL——这等于退化回拼接,防不了所有注入。
必须显式关掉模拟,并确认 MySQL 版本支持原生预处理(5.1.17+ 基本都行)。否则你以为安全了,其实没用。
```php
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$stmt = $pdo->prepare("INSERT INTO logs (msg, level) VALUES (?, ?)");
$stmt->execute([$msg, $level]);
```
- 关掉
PDO::ATTR_EMULATE_PREPARES后,如果 SQL 语法错误会报在prepare()阶段,而不是execute()时 - MySQLi 和 PDO 的预处理行为一致,但 PDO 支持命名参数(
:email),可读性略好 - 别信“PDO 更安全”的模糊说法——配置不对,它一样放行恶意输入
哪些地方容易漏?连 WHERE IN 和 ORDER BY 都得小心
批量 ID 查询(WHERE id IN (?))不能只绑一个参数,? 只代表单个值。动态字段排序(ORDER BY ?)会被拒绝,因为参数化不接受结构关键词。
这些不是“预处理不支持”,而是设计上就要求你用其他方式控制——白名单、枚举、硬编码分支,而不是把控制权交给用户输入。
-
IN列表:先用array_map()转成等量?,再implode()拼占位符,最后逐个绑定 -
ORDER BY字段名:只允许从['name', 'created_at', 'score']这类数组里取,用in_array()校验 - 分页
LIMIT:偏移量和数量必须是整数,用(int)强转,别用参数化
真正难的从来不是写对一行 prepare(),而是所有接收外部输入的地方——包括 URL 参数、HTTP 头、文件名、甚至 $_SERVER 变量——都得过一遍校验逻辑。漏一个,前面全白干。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











