分页查询中order by和limit是sql注入高发区,因字段名和排序方向无法参数化,须用白名单校验字段与方向,limit/offset数值需强制转int并设上限,django orm的order_by也需白名单过滤且禁用双下划线穿透。

分页查询里的 ORDER BY 和 LIMIT 是 SQL 注入高发区——因为它们不能用参数化占位符直接传参,很多人就退回去拼字符串,结果把整个查询拖进危险区。
ORDER BY 字段名不能用 %s 占位符
psycopg2、pymysql、sqlite3 都不支持把字段名或排序方向(ASC/DESC)作为参数传给 execute()。比如下面这行代码会报错或被当成字面量:
cursor.execute("SELECT * FROM user ORDER BY %s", (sort_field,)) # ❌ 错误:字段名不能参数化
真正能走参数化的只有值(如 WHERE name = %s),结构部分必须提前控制。
- 只允许白名单内的字段名:
allowed_sort_fields = {"id", "name", "created_at", "status"} - 排序方向也需校验:
if order_dir not in ("ASC", "DESC"): order_dir = "ASC" - 拼接前做严格检查:
if sort_field not in allowed_sort_fields: sort_field = "id"
LIMIT/OFFSET 的数值参数必须转成 int
LIMIT 和 OFFSET 虽然接受数字,但若用户传的是字符串(如 "10; DROP TABLE user;"),而你没做强制类型转换,某些驱动可能静默执行或触发意外行为。
- 必须显式转
int,并捕获ValueError:limit = int(request.GET.get("limit", "10")) - 设合理上限(如
max_limit = 100),防止恶意拉取全表:limit = min(limit, max_limit) - 不要用字符串格式化拼接:
f"LIMIT {limit}"或"LIMIT %s" % limit都是漏洞入口
Django ORM 分页也要防字段名注入
order_by() 看似安全,但如果字段名来自用户请求(如 request.GET.get("sort")),就等于把表结构暴露给攻击者:
queryset = User.objects.all().order_by(request.GET.get("sort")) # ❌ 危险:可传 "email__isnull" 或 "profile__user__password"
攻击者可能利用双下划线穿透关联字段,甚至触发 N+1 查询或敏感字段读取。
- 必须白名单过滤:
sort = request.GET.get("sort", "id"); if sort not in ["id", "name", "-name", "created_at"]: sort = "id" - 禁止带双下划线的字段:
if "__" in sort: sort = "id" -
extra(order_by=[...])和raw()更要小心——它们绕过 ORM 校验,所有字段名、表名都得手动白名单
最容易被忽略的一点:白名单不是写死在函数里就完事了;它得和数据库 schema 同步更新,否则字段重命名或新增后,旧白名单会拦住合法请求,而开发者可能因“功能异常”又悄悄放宽校验——那等于开了后门。











