唯一可靠的方式是用PDO::prepare或mysqli_prepare做参数化查询;其他如mysql_real_escape_string和intval()因依赖字符集、仅限字符串、无法防逻辑篡改等缺陷而不可靠,且预处理需配charset、禁用模拟、显式绑定类型,表名列名等结构必须白名单校验。

唯一可靠的方式是用 PDO::prepare 或 mysqli_prepare 做参数化查询,其他任何字符串拼接、转义函数或类型转换都不能替代它。
为什么 mysql_real_escape_string 和 intval() 不够用
这两个函数常被误当作“防注入万能解”,但它们只解决局部问题,且极易失效:
-
mysql_real_escape_string依赖连接字符集,若没显式调用mysqli_set_charset($conn, 'utf8mb4'),宽字节注入可绕过 - 它只对字符串字段起作用;数字型参数如
"WHERE id = " . $_GET['id']根本不加引号,mysql_real_escape_string完全不生效 -
intval()可防止非数字传入,但无法防护id=1 OR 1=1这类在数字上下文中仍能触发逻辑篡改的攻击(因为 PHP 会把整个字符串转成1,而 SQL 解析时已脱离 PHP 控制) - 所有这类补丁式处理,都建立在“输入格式可控”前提下,一旦业务需要支持含符号的用户名、邮箱、JSON 字段等,立刻崩塌
PDO 预处理必须配上的三个关键设置
只调用 prepare() 不等于安全。以下三点漏一,就可能退化为模拟预处理或编码绕过:
- 连接 DSN 中必须显式声明字符集:
mysql:host=localhost;dbname=test;charset=utf8mb4—— 缺少charset=utf8mb4是宽字节注入温床 - 禁用 PDO 模拟预处理:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false)—— 否则 PDO 自己拼 SQL,服务端根本没收到预编译指令 - 绑定时指定参数类型:
$stmt->bindValue(':status', $_POST['status'], PDO::PARAM_STR),避免自动类型推断导致意外截断或隐式转换
哪些地方预处理也救不了,必须白名单校验
预处理语句只保护「值」(value),不保护「结构」(identifier)。以下内容永远不能来自用户输入,必须硬编码或从白名单中选取:
立即学习“PHP免费学习笔记(深入)”;
- 表名、列名:
"SELECT * FROM {$_GET['table']}"—— 危险!应写死或用in_array($_GET['table'], ['users', 'orders'])校验 - 排序方向:
"ORDER BY name {$_GET['order']}"—— 危险!应限定为$order = in_array($_GET['order'], ['ASC', 'DESC']) ? $_GET['order'] : 'ASC' - LIKE 模糊匹配通配符位置:
"WHERE name LIKE '%{$_GET['q']}%'"—— 危险!应在 PHP 层拼好:$search = '%' . $_GET['q'] . '%'; $stmt->execute([$search]);
最易被忽略的一点:哪怕用了预处理,如果错误地把占位符写进 SQL 字符串里(比如 "WHERE name = '{$_POST['name']}'"),那整个 prepare 就只是个摆设——SQL 解析器看到的仍是拼接后的语句,参数绑定根本没机会介入。



















