真正有效的sql注入防护只有预处理+绑定参数:php中必须用pdo::prepare()+execute()或mysqli_prepare()+bind_param(),动态标识符需白名单校验,字符集须统一为utf8mb4,orm中raw方法仍需手动防护。

MySQL参数化查询必须用预处理语句
直接拼接字符串进SQL,哪怕加了mysql_real_escape_string(已废弃)或addslashes,都挡不住绕过型注入。真正有效的只有预处理+绑定参数这条路。
PHP里必须用PDO或mysqli的预处理接口,不能只靠“手动转义”糊弄:
-
PDO::prepare()+PDOStatement::execute(),参数走数组传入,数据库引擎自己区分数据与语句结构 -
mysqli_prepare()+mysqli_stmt_bind_param(),类型必须显式声明("s"、"i"、"d"),否则绑定无效 - 别用
mysqli_real_escape_string()处理用户输入后拼进SQL——它不防二阶注入,也不防宽字节注入(尤其gbk连接下)
哪些地方容易漏掉参数化
最常翻车的是动态字段名、表名、排序字段、LIMIT偏移量——这些不能当参数绑定,因为预处理只允许参数化**值**,不允许参数化**标识符**。
比如ORDER BY ?会报错;LIMIT ?, ?中第一个?在MySQL里也不被支持(5.7+才部分支持,但不可靠)。
- 字段/表名必须白名单校验:
in_array($field, ['name', 'created_at', 'status'], true) - LIMIT偏移量必须强转整型:
(int)$offset,且检查是否非负 - IN语句里的多个值不能只绑一个?,得动态生成占位符:
WHERE id IN (?, ?, ?),再逐个绑定
字符集配置不当会让转义失效
即使用了mysqli_set_charset($conn, 'utf8mb4'),如果MySQL服务端默认字符集是latin1,或者连接时没指定charset=utf8mb4,某些多字节字符仍可能触发宽字节注入。
- PHP连接DSN里必须显式带
;charset=utf8mb4(PDO)或调用mysqli_set_charset()(mysqli) - 检查
SHOW VARIABLES LIKE 'character_set%',确保character_set_client、character_set_connection、character_set_results三者一致且为utf8mb4 - 表和字段也得是
utf8mb4_unicode_ci,否则索引可能不生效,还影响emoji存储
ORM和Query Builder不是免检金牌
Laravel的DB::table()->where()、ThinkPHP的where()默认是参数化的,但一旦混用whereRaw()、selectRaw()或DB::raw(),就立刻退出安全区。
-
whereRaw('price > ?', [$minPrice])安全;whereRaw("price > {$minPrice}")危险 - 拼接
ORDER BY时,别写orderBy(DB::raw($request->input('sort'))),应先白名单过滤 - Django的
extra()、SQLAlchemy的text()同理——raw SQL段落必须零容忍人工拼接
参数化不是开关,是每条SQL路径都要确认的约束。漏一处,整张表就可能裸奔。











