LIMIT本身不能防SQL注入,真正起作用的只有参数化查询;攻击者可通过UNION SELECT、多语句等绕过LIMIT限制,因此必须对LIMIT参数强制转整型、设硬上限并配合白名单校验动态结构。

LIMIT 本身不能防 SQL 注入,它只管“返回多少”,不管“语句是否被篡改”。攻击者用 UNION SELECT、INSERT、SELECT ... INTO OUTFILE 或多语句(如 ; 后接 DROP TABLE)时,LIMIT 完全无效。真正起作用的只有参数化查询 —— LIMIT 只能在参数化前提下,作为辅助手段控量、防拖库、减小危害面。
为什么直接拼接 LIMIT 参数等于开门揖盗
常见错误写法:$limit = (int)$_GET['limit']; $sql = "SELECT * FROM users LIMIT $limit";。表面看转了整型,但一旦上游没做严格校验(比如传 limit=100; DROP TABLE users),或 PHP 配置允许 register_globals 等老旧机制,就可能触发多语句执行。更隐蔽的是:整型转换不拦截负数或极大值,9223372036854775807 这类值会让 MySQL 实际返回全部数据,失去限制意义。
正确做法必须满足两个条件:
- 所有用户输入(包括
LIMIT值)必须走bindValue()或等效绑定,且显式指定类型为PDO::PARAM_INT -
LIMIT值必须设硬上限(如最多100),超出则截断或拒绝,不能依赖前端传值
不同数据库对 LIMIT 参数化的支持差异
MySQL 和 PostgreSQL 支持直接绑定 LIMIT ?,但 SQLite 会报错:near "+": syntax error 如果你写 LIMIT ? + 1。PostgreSQL 不接受负数 LIMIT,0 表示空结果集;MySQL 允许负数但会转为 0,行为不一致。这些细节不处理,上线后容易在某套环境突然崩。
安全写法示例(PDO + MySQL):
SELECT id, name FROM users WHERE status = ? LIMIT ?
执行时:
$stmt->bindValue(1, $status, PDO::PARAM_STR);<br>$stmt->bindValue(2, min((int)$limit, 100), PDO::PARAM_INT);
表名、字段名、排序方向不能参数化,白名单是唯一解
哪怕 LIMIT 绑定了,如果代码里这样写:"ORDER BY $sort_field $sort_dir",攻击者传 sort_field=id; DROP TABLE logs 就直接中招。这类动态 SQL 片段无法用占位符,必须用白名单硬控制:
-
$allowed_tables = ['users', 'orders', 'products'],校验后再拼 -
$allowed_fields = ['id', 'name', 'created_at'],且需映射到真实字段(避免别名暴露逻辑) -
ASC/DESC必须从固定字符串中取,不能直接拼用户输入
最容易被忽略的点:开发常以为“只要用了 ? 就安全了”,却把表名、分页偏移量(OFFSET)、GROUP BY 字段全当变量拼进去 —— 这些地方没有占位符语法,白名单校验漏一项,整条查询就失效。


















