PHP 8 环境防止 SQL 注入的核心是强制使用 PDO 或 MySQLi 预处理语句,并显式关闭模拟预处理(PDO::ATTR_EMULATE_PREPARES => false),动态标识符必须通过白名单校验。
先说结论:仅调用 prepare() 不足以保证安全,必须关闭模拟预处理并配合参数绑定,非值上下文需白名单过滤。
- 先判断:检查代码是否仍存在字符串拼接 SQL 或使用了已废弃的 mysql_* 函数。
- 优先做:在 PDO 构造时显式设置 ATTR_EMULATE_PREPARES 为 false,所有用户输入走占位符绑定。
- 再验证:通过获取 PDO 属性确认模拟预处理已关闭,并使用特殊字符 payload 测试查询是否报错或异常。
快速处理思路
若无法立即重构全部代码,优先确保数据库连接配置正确,并将高风险查询改为预处理模式。以下是 PDO 安全连接的标准配置片段:
<?php
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
对于 MySQLi,确保调用 bind_param() 时变量传递引用,且类型标识符与变量顺序严格匹配。
为什么会这样
PHP 8 默认仍可能开启 PDO 模拟预处理,导致 prepare() 仅在 PHP 层替换字符串而非数据库层预编译。若数据库字符集配置不当(如未显式设置 utf8mb4),攻击者可利用宽字节或特殊编码绕过转义。关闭模拟预处理后,SQL 语句结构与数据在数据库层分离,用户输入无法改变查询逻辑。此外,预处理仅保护值(Value),表名、字段名、ORDER BY 子句等标识符(Identifier)无法绑定,必须通过代码层白名单限制。
分步处理
1. 修正连接配置:实例化 PDO 时传入选项数组,显式禁用模拟预处理,并设置错误模式为异常抛出,便于捕获潜在问题。
2. 改造查询语句:将所有用户输入替换为占位符(? 或 :name),禁止直接拼接 $_GET、$_POST 变量到 SQL 字符串中。数字类型也需使用占位符并绑定 PDO::PARAM_INT。
3. 处理动态标识符:对于表名、排序字段等无法绑定的部分,定义允许值的数组(白名单),使用 in_array 校验用户输入,非法则 fallback 到默认值。
4. 输入验证辅助:在绑定前使用 filter_var 校验数据格式(如邮箱、IP),虽不能替代预处理,但可减少无效数据进入查询层。
怎么验证是否生效
执行代码 $pdo->getAttribute(PDO::ATTR_EMULATE_PREPARES),若返回 false 则配置生效。尝试输入包含单引号或 SQL 关键字(如 UNION SELECT)的测试数据,若页面未报错且查询结果未异常增多,说明注入被拦截。检查数据库日志,确认发送的语句是否为预编译形式而非完整拼接字符串。
常见坑
1. 混用占位符:同一语句中不能混用命名参数(:name)和问号占位符(?),会导致绑定错位。
2. mysqli 绑定陷阱:bind_param() 要求变量必须传引用,不能直接传表达式(如 (int)$_GET['id']),需先赋值给变量再绑定。
3. 密码校验逻辑:不要在 SQL 中比对密码哈希,应先查出哈希值再用 password_verify() 在 PHP 层验证,避免暴露逻辑。
4. 框架 raw 接口:使用 Laravel 或 ThinkPHP 时,避免直接使用 raw() 或 exp() 接口拼接用户输入,除非已做过白名单处理。
常见问题
mysqli_real_escape_string 能代替预处理吗?
不能,该函数依赖字符集设置且无法防护数字型参数或非值上下文,现代 PHP 开发应优先使用预处理语句。
ORDER BY 后面能用占位符吗?
不能,MySQL 语法不允许参数化排序字段,必须通过白名单校验字段名后拼接到 SQL 中。
PHP 8 移除了 mysql_* 函数吗?
是的,PHP 8.0 及以上版本已完全移除 mysql_* 扩展,继续使用会导致 fatal error,必须迁移至 PDO 或 MySQLi。
参考来源
- PHP 最新版如何防止 SQL 注入_PHP 最新版防止 SQL 注入技巧【安全】
- 如何在 PHP 8 中彻底防御 SQL 注入漏洞_通过 PDO 预处理语句实现安全查询
- PHP 8.5.5 如何防止 SQL 注入攻击【安全】
- php 如何防止 sql 注入_预处理语句安全防护方法【教程】
- PHP 怎么防止 SQL 注入攻击_PHP 安全过滤输入方法【技巧】