如何修复MySQL项目中的注入漏洞

作者:袖梨 2026-08-31

addslashes不能防SQL注入,因其仅转义特定字符且不感知SQL语义与字符集,对数字型参数、宽字节绕过、二次注入等完全无效;唯一可靠方案是PDO或MySQLi预处理绑定参数并禁用模拟预处理。

唯一可靠方式是用 PDO::prepare()mysqli_prepare() 做预处理,其他手段(如 addslashes()、正则过滤、关键词黑名单)在真实攻击场景下基本失效。

为什么 addslashes() 不能防注入

它只转义单引号、双引号、反斜杠和 NULL 字符,对数字型注入(如 id=1 OR 1=1)完全无效;更致命的是,它不感知字符集——当 MySQL 连接层用 GBK 而 PHP 没显式调用 mysqli_set_charset() 时,%A1%27 这类宽字节可绕过转义;且一旦数据从数据库读出再拼进新 SQL(二次注入),addslashes() 就彻底没用了。

  1. addslashes() 不区分上下文:字符串、数字、表名都用同一套规则,但数据库解析这三类值的语法完全不同
  2. 它无法阻止 WHERE id = ? 中传入字符串 "1 OR 1=1" —— mysqli 可能隐式转换,PDO 在 PDO::ATTR_EMULATE_PREPARES = true(默认)时也可能出问题
  3. 现代 MySQL 默认 utf8mb4,但连接字符集不显式设置,仍可能触发宽字节漏洞

PDO::prepare() 和 mysqli_prepare() 怎么选

优先用 PDO:抽象层稳定,错误模式统一(如 PDO::ERRMODE_EXCEPTION),占位符支持命名(:username)和问号(?),迁移旧代码更平滑;若项目已重度依赖 mysqli,必须开启 MYSQLI_USE_PREPARE 模式,并在连接后立即调用 mysqli_set_charset($conn, 'utf8mb4')

  1. mysqli 只支持问号占位符,绑定参数必须用 bind_param(),类型字符(s/i/d)写错会导致静默失败(比如该传 i 却写了 s
  2. 两者都要求 prepare 前已完成连接初始化,且预处理语句对象不能跨连接复用
  3. PDO 默认启用模拟预处理(PDO::ATTR_EMULATE_PREPARES = true),需显式设为 false 才走真正服务端预编译

参数绑定前必须做输入校验

预处理防注入,但不防业务逻辑错误。例如用户传 id=-999id=99999999999999999999,可能触发越权、整数溢出或隐式类型转换漏洞。

  1. 数字型参数建议用 filter_var($x, FILTER_VALIDATE_INT)intval() 清洗,再传给 bind_param()execute()
  2. 枚举类参数(如 status=active)必须白名单校验:in_array($status, ['active', 'inactive'], true),不能只靠预处理
  3. 表名、字段名等标识符无法用占位符,必须硬编码或严格白名单映射(如 $table_map = ['user' => 'users', 'log' => 'logs']

最容易被忽略的执行环节

写了 prepare 却仍被绕过,问题常出在周边链条断裂:忘记检查 execute() 返回值,导致绑定失败被吞掉;用 mysqli_query() 直接执行拼接 SQL(哪怕只在日志或调试分支里);事务未关闭自动提交,错误时回滚失败。

  1. mysqli_stmt::execute()PDOStatement::execute() 都返回布尔值,必须判断,否则参数绑定失败时查不到数据却无报错
  2. 不要混用:一个查询里 prepare 了部分参数,其余还用字符串拼接
  3. 所有数据库操作应封装在统一入口,禁止裸调 mysqli_query()PDO::query()

修复的核心不在“怎么写预处理”,而在“怎么确保每条 SQL 都走预处理”——漏掉一处拼接,整个防御就崩塌。最危险的不是不会写,而是写了却没全覆盖。

相关文章

精彩推荐