必须使用服务端预处理参数化查询并禁用模拟模式,严格校验动态sql部分,配置mysql安全模式与权限。

用 mysql_real_escape_string 已经不行了,别再硬套老教程
PHP 5.5+ 已废弃 mysql_real_escape_string,7.0+ 彻底移除。靠转义字符串防注入是幻觉——攻击者早就能绕过单引号逃逸、宽字节注入、二次注入等场景。真正有效的只有参数化查询,且必须配合正确的驱动和执行方式。
- MySQLi 和 PDO 都支持预处理,但
PDO::ATTR_EMULATE_PREPARES默认开启时,PDO 会模拟预处理(即拼 SQL + 转义),等于白搭 - 必须显式关闭:设置
PDO::ATTR_EMULATE_PREPARES为false,让 MySQL 服务端真正执行预处理 - 不要用
mysqli_real_escape_string拼接WHERE name = '$name'—— 即使函数调用成功,也已落入陷阱
PDO 预处理必须关掉模拟,否则等于没做
很多线上项目配了 PDO,却始终开着 PDO::ATTR_EMULATE_PREPARES = true。这时你写的 $stmt->execute([':id' => $_GET['id']]) 实际仍是 PHP 层拼 SQL,MySQL 根本不知道这是预处理。
- 正确初始化示例:
$pdo = new PDO($dsn, $user, $pass, [ PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ]); - 检查是否生效:执行
$pdo->prepare("SELECT ?"),若抛出SQLSTATE[HY000]: General error,说明服务端预处理已启用;若不报错,大概率还在模拟 - MySQL 5.7+ 默认允许服务端预处理;但低版本或特殊配置(如 max_prepared_stmt_count=0)会静默退回到模拟模式
过滤输入 ≠ 防注入,intval() 和 filter_var() 只能当辅助
对 ID 类整数字段用 intval($_GET['id']) 看似安全,但一旦业务扩展成 ORDER BY ? 或 LIMIT ?,?,类型断言就失效。字符串字段更不能靠 trim() 或正则替换“危险词”来防御。
-
ORDER BY后的字段名不能参数化,必须白名单校验:比如只允许['created_at', 'status', 'score'] -
LIMIT的偏移量和数量可用intval(),但需额外判断非负、上限(如min($offset, 10000)) - 任何拼进 SQL 的动态部分(哪怕只是表名、列名、关键字),都必须走严格白名单,不能依赖“看起来像数字”或“不含单引号”
MySQL 本身没“安全插件”可装,sql_mode 和账户权限才是第一道防线
不存在叫 “MySQL Secure Plugin” 的官方插件。所谓“安全加固”,其实是配置层面的收敛:禁用高危函数、限制用户权限、收紧 SQL 模式。
- 在
my.cnf中设置sql_mode = STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,避免隐式类型转换导致绕过 - 业务账号禁止
FILE、PROCESS、SUPER权限;读写账号分离,写账号不授予SELECT全库权限 - 禁用
LOAD_FILE()、SELECT ... INTO OUTFILE等函数,可在启动参数加--secure-file-priv=/dev/null
mysqli_multi_query)和存储过程中拼接 SQL 的场景——这两处连预处理都救不了,只能靠设计规避。别让业务逻辑把 SQL 组装责任推给 DAO 层。











