limit和offset不能使用参数化查询,必须通过强制转整型+范围校验后拼接;未校验时攻击者可利用隐式转换、超大数或堆叠注入实施攻击。

直接在 LIMIT 子句里拼接用户输入,是典型的“看似 harmless 实则高危”操作——它绕过了绝大多数参数化查询的保护机制,因为主流数据库(MySQL、PostgreSQL)不支持对 LIMIT 的占位符绑定(? 或 $1)。
为什么 LIMIT 不能用 ? 占位符?
这不是你代码写错了,而是数据库协议本身的限制:预处理语句只允许参数化 **值**(value),不允许参数化 **语法结构**(如表名、列名、ORDER BY 字段、LIMIT 偏移量/数量)。MySQL 的 PreparedStatement 明确拒绝 LIMIT ?,会抛出 SQLSyntaxError 或类似错误;PostgreSQL 的 PREPARE 同样不接受 LIMIT $1。
常见错误现象:
- Java 报错:java.sql.SQLException: Parameter index out of range(即使只传一个参数)
- Python + psycopg2 报错:psycopg2.errors.SyntaxError: syntax error at or near "$1"
- Node.js + mysql2 报错:ER_PARSE_ERROR: You have an error in your SQL syntax
- 不是驱动 bug,是 SQL 标准和协议设计决定的
- ORM 框架(如 Hibernate、Django ORM)内部也必须手动校验
limit/offset参数,无法靠绑定自动防护 - 攻击者只要控制
LIMIT后的数字,就能触发堆叠注入(如LIMIT 1; DROP TABLE users;),前提是数据库支持多语句且连接未禁用
如何安全地处理 LIMIT 和 OFFSET?
核心原则:把用户输入的数字当作「纯整数」来对待,而不是字符串拼接。必须做类型强制 + 范围白名单校验。
- 先转成整数(不是字符串 trim 后直接拼):Python 用
int(request.args.get('limit', '20')),Java 用Integer.parseInt(request.getParameter("limit")) - 再检查是否在合理区间内:比如
limit限定在1–100,offset限定在0–10000,超出即拒绝或截断 - 绝对不要做字符串拼接后过滤单引号——
"LIMIT " + clean_input依然危险,因为攻击者可输入10; DROP TABLE--这类绕过过滤的 payload - 如果业务真需要动态 limit(比如导出全部数据),应改用分页 API + 游标(cursor-based pagination),彻底避开
LIMIT/OFFSET
MySQL 中 LIMIT 注入的典型绕过手法
攻击者知道你做了整数转换,但没做范围限制时,会利用 MySQL 的隐式类型转换特性:
- 输入
10+0→ 仍是整数,但可能绕过简单正则(如/^\d+$/) - 输入
1e1→ MySQL 解析为 10,但int()在 Python 中会报错,JavaparseInt也会失败,说明校验逻辑不一致 - 输入超大数如
9999999999999999999→ 可能导致整数溢出、数据库慢查询甚至内存耗尽(DoS) - 在支持多语句的场景下(如 PHP
mysqli_multi_query),LIMIT 10; SELECT @@version;可执行第二条语句
这些都不是理论漏洞,而是真实渗透测试中高频复现的 bypass 路径。
不同语言的安全写法示例
Python(Flask):
limit = request.args.get('limit', '20')
try:
limit = max(1, min(100, int(limit))) # 强制范围
except (ValueError, TypeError):
limit = 20
query = f"SELECT * FROM posts ORDER BY id DESC LIMIT {limit}" # 此处拼接安全,因 limit 是受控整数
Java(JDBC):
String limitParam = request.getParameter("limit");
int limit = 20;
try {
limit = Math.max(1, Math.min(100, Integer.parseInt(limitParam)));
} catch (NumberFormatException e) {
// log and use default
}
String sql = "SELECT * FROM posts ORDER BY id DESC LIMIT " + limit;
关键点:所有拼接都发生在整数校验之后,且范围硬编码(不可从配置读取,避免配置被污染)。
真正容易被忽略的,是 OFFSET —— 它和 LIMIT 一样危险,但开发者常只校验 LIMIT,放任 OFFSET 接收任意大数,导致深分页性能崩塌或被用于盲注探测。校验必须双管齐下。











