唯一可靠防sql注入的方法是使用pdo::prepare()或mysqli_prepare()预处理语句,因其将sql结构与数据彻底分离;禁用模拟预处理、动态标识符须白名单校验,addslashes()和mysql_real_escape_string()等函数已废弃且不安全。

PDO 和 mysqli_prepare 是 PHP 防御 SQL 注入最可靠的方式,其他所谓“过滤函数”或“转义函数”在复杂场景下极易失效。
用 PDO::prepare() 替代 mysql_real_escape_string()
老项目里常见 mysql_real_escape_string(),但它只对单字节字符集有效,且无法防御宽字节注入、多语句注入等变种。现代 PHP(7.4+)已彻底移除该函数。
正确做法是统一使用 PDO 并开启 PDO::ATTR_EMULATE_PREPARES = false:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ? AND status = ?");
$stmt->execute([$_GET['id'], 'active']);
- 必须关闭模拟预处理(
PDO::ATTR_EMULATE_PREPARES = false),否则 PHP 会在客户端拼接 SQL,失去防护意义 - 占位符只能用于数据值,不能用于表名、列名、排序方向(
ORDER BY ?是非法的) - 如果真要动态列名,只能白名单校验:
in_array($_GET['sort'], ['name', 'created_at'], true)
mysqli 下必须用 mysqli_prepare(),不是 mysqli_real_escape_string()
很多开发者误以为 mysqli_real_escape_string() 能替代预处理——它不能。该函数只是做字符串转义,仍可能被绕过(如 GBK 宽字节漏洞、JSON 字段内嵌 SQL 等)。
正确写法:
$mysqli = new mysqli($host, $user, $pass, $db);
$stmt = $mysqli->prepare("INSERT INTO logs (ip, action) VALUES (?, ?)");
$stmt->bind_param("ss", $_SERVER['REMOTE_ADDR'], $_POST['action']);
$stmt->execute();
-
bind_param()的第一个参数是类型字符串:s(string)、i(integer)、d(double)、b(blob) - 不要用
mysqli_query($mysqli, "INSERT ... '".mysqli_real_escape_string($mysqli, $x)."'")—— 这和拼接字符串没本质区别 - 注意:
mysqli_prepare()不支持多语句(INSERT; SELECT),这是好事,反而堵住了一类攻击面
别在应用层“自己造轮子”过滤输入
OWASP Top 10 2025 明确指出,“输入验证”只是辅助手段,不能替代参数化查询。试图用正则过滤 '、;、--、/* 等符号,既不全面也不可持续。
- 攻击者可绕过:用十六进制编码(
0x756e696f6e)、Unicode 同形字、注释变形(/!UNION/)、或利用 JSON/NoSQL 特性发起非 SQL 类注入 - 业务字段本身就需要特殊字符(如用户昵称含单引号、地址含分号),粗暴过滤会破坏功能
- 真正该做的,是在输出到 HTML 时用
htmlspecialchars(),在输出到 JS 上下文时用json_encode(),在输出到 URL 时用urlencode()—— 场景决定编码方式,不是统一“过滤”
为什么你写了 prepare() 还被扫出 SQL 注入?
常见原因不是函数没用,而是调用姿势错了:
- 用了
PDO::query()或mysqli_query()执行带变量的字符串 —— 即使前面声明了prepare对象也没用 - 把用户输入拼进了 SQL 模板里:
"SELECT * FROM {$table} WHERE ..."—— 表名不可能参数化,必须白名单限制 - 在 ORM 中混用原生查询(如 Laravel 的
DB::select(DB::raw("..."))),绕过了 ORM 的参数绑定机制 - 错误地认为“用了框架就安全”,但框架的
whereRaw()、orderByRaw()等方法默认不绑定参数
参数化查询的防护边界非常清晰:只保数据值,不保语法结构。任何把用户输入塞进 SQL 字符串骨架里的操作,都在防线之外。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











