唯一靠谱的防sql注入方法是使用mysqli_prepare或pdo::prepare预处理,禁用模拟预处理、严格绑定参数、动态标识符白名单校验、妥善管理statement生命周期、避免orm中拼接、严查存储过程拼接逻辑。

用 mysqli_prepare 或 PDO::prepare 是唯一靠谱的起点
不预处理,光靠 mysqli_real_escape_string 或字符串拼接,高并发下照样被绕过。原因很简单:逃逸函数依赖字符集上下文,而连接层、客户端、服务端三方字符集不一致时(比如连接时没显式设 SET NAMES utf8mb4),\' 可能被 reinterpret 成多字节字符的一部分,逃逸失效。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 统一用
PDO,开启PDO::ATTR_EMULATE_PREPARES = false(否则 PDO 会退化为模拟预处理,等同于拼接) - 绑定参数必须用
bindValue或bindParam,别用execute([$value])看似简洁——PHP 7.4+ 对数组参数类型推断松散,遇到null或数字字符串可能触发隐式类型转换,破坏绑定语义 - 查询语句里不要把表名、字段名做成参数(
PDO和mysqli都不支持),这类动态部分必须白名单校验,比如用in_array($table, ['user', 'order'], true)
高并发下 prepare 失败常因连接未复用或 statement 泄露
现象是报错 MySQL server has gone away 或 Commands out of sync,尤其在长连接池(如 Swoole、PHP-FPM 持久连接)中高频出现。根本不是 SQL 注入问题,而是 prepare statement 生命周期管理失控。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 每个
PDOStatement实例用完立刻 unset,别依赖 GC;循环内反复 prepare 同一语句却不释放,MySQL 侧会累积 statement 句柄,达到max_prepared_stmt_count就拒绝新 prepare - 确认 MySQL 配置项
wait_timeout和应用层连接空闲超时匹配,否则连接被服务端断开后,应用还拿着旧连接调prepare,必然失败 - 若用 Swoole,禁止在协程间共享
PDO实例或PDOStatement——它们不是线程/协程安全的,必须 per-request 新建或从连接池取隔离实例
ORM 不能当防注入保险柜,Laravel Eloquent 也得看怎么用
很多人以为用了 Eloquent 就自动免疫,结果写出 User::whereRaw("name = '{$_GET['name']}'")->get() 还浑然不觉。Eloquent 的 whereRaw、DB::select(DB::raw(...))、作用域里拼接字符串,全都会绕过参数绑定。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
-
whereRaw后面的占位符必须配bindings参数,写成whereRaw('name = ?', [$name]),而不是whereRaw("name = '$name'") - 避免在模型 accessor/mutator 里执行原生查询;更危险的是在
boot静态方法里写DB::statement拼接,这种代码上线后几乎没人 review - 如果必须动态字段,用
DB::table()->select($allowedColumns),且$allowedColumns必须来自硬编码数组或配置文件,别从请求参数直接映射
别忽略存储过程和触发器里的拼接逻辑
后端 PHP 层做了严防死守,但 DBA 在 MySQL 里写了 CONCAT('SELECT * FROM ', table_name) 的存储过程,再用 PHP 调用 CALL search_by_table(?)——参数进的是存储过程,但拼接发生在服务端,PDO 绑定完全无效。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 所有存储过程入参,凡涉及拼接到 SQL 字符串的,必须用
INFORMATION_SCHEMA白名单校验表名/字段名,例如SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = given_table - 禁用
PREPARE ... EXECUTE动态执行(MySQL 5.7+ 默认禁止非 SUPER 权限用户这么做,但开了也没用——它不走参数绑定) - 审计存量存储过程时,重点搜
CONCAT(、CONCAT_WS(、EXECUTE IMMEDIATE这类关键词
真正难的不是写对一条 prepare,而是确保整条链路——从 HTTP 请求参数,到 PHP 变量流转,再到 ORM 方法选择,最后到数据库侧的存储逻辑——没有一处偷偷拼接。漏掉任意一环,高并发只是让漏洞更快暴露而已。










