PHP 防 SQL 注入速查表:6 条规则覆盖 99% 场景

CLARA轻量论坛系统
CLARA轻量论坛系统 星耀SVIP管理员 黑卡会员
发布于 2026-09-15 18:16 ·9 浏览 ·0 回复

结论:PHP 防 SQL 注入的核心只有一句——让用户数据永远以「参数」的身份进入数据库,绝不参与 SQL 语句拼接。剩下 5 条规则,都是在处理「参数覆盖不到的位置」和「出事之后的兜底」。掌握这 6 条,日常开发 99% 的注入场景都能挡住。

规则一:所有变量走预处理占位符,禁止拼接

预处理(prepared statement)是把 SQL 语句和数据分开传给 MySQL,数据库先编译语句结构,再填值,值里就算带 `' OR 1=1 --` 也只会被当成普通字符串。这是唯一能根治注入的手段。

// 正确:PDO 命名占位符
$stmt = $pdo->prepare('SELECT id, username FROM users WHERE email = :email AND status = :status');
$stmt->execute([':email' => $email, ':status' => 1]);
$user = $stmt->fetch();

// 错误:任何形式的拼接都是漏洞
$sql = "SELECT * FROM users WHERE email = '$email'"; // 别这么写

三个必须记住的参数:PDO 连接时设 `PDO::ATTR_EMULATE_PREPARES => false`(关闭模拟预处理,走 MySQL 真预处理)、`PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION`(错误抛异常而不是静默)、`charset=utf8mb4` 写进 DSN。

规则二:表名、列名、排序字段用白名单映射

结论:占位符只能替换「值」,不能替换「标识符」,所以表名、列名、`ORDER BY` 字段必须走白名单数组,不能用用户输入。

$allowSort = ['created_at', 'views', 'reply_count'];
$sort = in_array($_GET['sort'] ?? '', $allowSort, true) ? $_GET['sort'] : 'created_at';
$order = ($_GET['order'] ?? '') === 'asc' ? 'ASC' : 'DESC';
$sql = "SELECT * FROM posts ORDER BY {$sort} {$order} LIMIT :limit";

注意点:白名单判断一定要用 `in_array(..., true)` 严格模式;`$sort` 从白名单里取出,虽然拼进了 SQL,但它已经不是一个「用户可控字符串」了。

规则三:IN 查询按数组长度动态生成占位符

结论:`IN (?)` 不能直接塞数组,必须按元素个数生成对应数量的占位符。

$ids = array_map('intval', $_POST['ids'] ?? []);   // 先强制整型
if (!$ids) { return []; }
$holders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT * FROM posts WHERE id IN ($holders)");
$stmt->execute($ids);

这里的 `?` 数量由数组长度决定,不来自用户输入的「内容」,所以安全。`intval` 是双保险,不是主要防线。

规则四:LIKE 的通配符在参数里拼,不在 SQL 里拼

结论:`LIKE` 的关键词要作为参数传入,`%` 加在 PHP 变量上,而不是写进 SQL 字符串。

$kw = '%' . str_replace(['%', '_'], ['\%', '\_'], $keyword) . '%';
$stmt = $pdo->prepare('SELECT * FROM posts WHERE title LIKE ?');
$stmt->execute([$kw]);

顺带把 `%` 和 `_` 转义掉,否则用户输入 `%` 会全表匹配,变成一次轻量 DoS。

规则五:数据库账号最小权限 + 关闭 SQL 错误回显

结论:即便代码写错了,权限和错误处理也能把损失控制在最小范围。

  • 给应用的 MySQL 账号只授 `SELECT/INSERT/UPDATE/DELETE`,不要给 `DROP`、`FILE`、`GRANT`,更不要用 root;
  • 生产环境 `display_errors = Off`,PDO 用异常模式把 SQL 报错写进日志,绝不 `echo` 到页面——报错信息会直接告诉攻击者表名和字段名;
  • 模糊测试时常见的 `' AND SLEEP(5)--` 之所以能生效,前提就是错误信息或响应差异被暴露出来。

规则六:统一封装 DB 层,让规则变成「不能违反」

结论:靠人记规则一定会漏,正确做法是把查询入口收敛成一两个函数。

查库只允许通过 `db_query($sql, $params)` 这类封装,`$params` 强制为数组;封装内部只走 `prepare + execute`,并对 SQL 模板里出现的标识符做白名单校验。这样新同事写业务代码时,就算不懂注入,也天然写不出漏洞。

这一点对无框架轻量项目尤其重要。像 Clara BBS 这类「上传即用、无需 Composer」的 PHP 社区系统(环境为 PHP 7.4-8.5 + MySQL 5.7+),代码直接跑在原生 PHP 上,没有框架 ORM 替你兜底;给它写插件、改模板时如果需要直接访问数据库,请务必自己套一层上述封装再查,别在 `content/plugins` 里裸写 `mysqli_query("... $var ...")`。另外要注意,系统后台的敏感词拦截、IP 封禁属于内容治理层,它们防的是灌水与骚扰,不能替代 SQL 层的预处理——两层各管一段,别混为一谈。

最后补一个易错点:`mysqli_real_escape_string` 这类转义函数只在「实在无法预处理」的历史代码里当兜底,且必须确认连接字符集已正确设置(如 `set_charset('utf8mb4')`),否则宽字节注入依然可能绕过。能用预处理就别用转义。

一句话收尾:预处理覆盖「值」,白名单覆盖「标识符」,最小权限和错误收敛覆盖「万一」,统一封装覆盖「下次」。六条各就各位,注入基本没有可乘之机。

本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-379.html
转载请注明出处,版权归原作者所有。

全部回复 0

还没有回复,来抢沙发~