mysqli_real_escape_string() 仅是语法转义工具,非防注入方案;宽字节下反斜杠被吞、数字型参数无效、动态标识符无法防护,必须用预处理+白名单。

它根本不是为防注入设计的,只是个语法转义工具;用它等于在 SQL 注入的雷区里闭眼走路。
宽字节编码下转义被“吃掉”
当 MySQL 连接字符集设为 gbk,但 PHP 没调用 mysqli_set_charset($conn, 'gbk'),mysqli_real_escape_string() 就按默认 latin1 转义。攻击者传入 %bf%27(即字节 0xbf 0x27),服务端解码后变成一个无效 gbk 字符 + 单引号,后者裸露执行。而 mysqli_real_escape_string() 在 latin1 下把 0x27 转成 0x5c 0x27,结果 0xbf 0x5c 0x27 在 gbk 中被识别为汉字 + 单引号——前面那个反斜杠被“吞”了。
- 检查当前连接字符集:
SELECT @@character_set_client, @@collation_connection - 必须显式设置:
mysqli_set_charset($conn, 'utf8mb4'),不能只靠SET NAMES -
addslashes()更危险:它完全不看字符集,纯字节操作,绕过更简单
数字型参数拼接时完全失效
写 "WHERE id = " . $_GET['id'],攻击者传 id=1 OR 1=1,整个条件变成 WHERE id = 1 OR 1=1。mysqli_real_escape_string() 对空格、OR、= 零处理,因为这些字符根本不在它的转义列表里。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 数字字段也必须走占位符:
"WHERE id = ?"+bind_param('i', $id) - 别用
(int)$_GET['id']图省事:非法输入如abc会被静默转成0,掩盖业务异常 - 框架若允许手写原生 SQL(如 Laravel 的
DB::select()),确认它底层是否调用PDO::prepare;否则得自己封装
动态标识符(表名、列名、ORDER BY)根本无法防护
"SELECT * FROM " . $_GET['table'] 或 "ORDER BY " . $_GET['sort'] 这类场景,连 ? 占位符都不支持。mysqli_real_escape_string() 对空格、分号、点号、括号完全无感,sort=name; DROP TABLE users-- 照样执行。
立即学习“PHP免费学习笔记(深入)”;
- 必须白名单校验:
in_array($_GET['sort'], ['name', 'email', 'created_at'], true) - 避免直接拼表名;真要支持多表,用配置映射:
$tables = ['user' => 'users', 'order' => 'orders'] - 二次注入中从 DB 读出的字段再拼 SQL(如
orderByRaw("{$sortField} {$direction}")),同样适用该规则——白名单动作必须紧贴prepare()或mysqli_query()调用前
最常被忽略的一点:所有这些缺陷不是“你用得不够仔细”,而是函数能力边界本身决定的。它不处理关键字、不校验上下文、不感知 SQL 结构,只在字符串引号层面做字节修补。一旦业务需要支持含符号的用户名、JSON 字段、或排序方向切换,这套逻辑就立刻崩塌。


















