ORDER BY字段名直接拼接是高危操作,需用白名单校验;LIMIT/OFFSET必须参数化,禁止字符串拼接,且须转int并范围检查。

ORDER BY 字段名直接拼接是高危操作
分页查询中,ORDER BY 后的字段名(如 "name"、"created_at")若来自用户请求(如 request.GET.get("sort")),又未经校验就直接拼进 SQL,等于把表结构暴露给攻击者。攻击者可传 id; DROP TABLE users-- 或 email ASC, (SELECT password FROM users LIMIT 1) 等非法字段,触发注入。
正确做法是白名单硬限制:
- 只允许预设字段:
allowed_sort_fields = {"id", "name", "email", "created_at"} - 显式校验并 fallback:
sort_field = request.GET.get("sort", "id"); sort_field = sort_field if sort_field in allowed_sort_fields else "id" - 拼接时仅用于字段名,不参与参数化:
f"ORDER BY {sort_field} {direction}"(direction同样需白名单限为"ASC"或"DESC")
LIMIT/OFFSET 参数必须走参数化,不能格式化字符串
LIMIT 和 OFFSET 是整数,但很多人误以为“只是数字”就安全,用 f"LIMIT {limit} OFFSET {offset}" 拼接——这是错的。攻击者可传 limit=20; SELECT * FROM secrets--(在某些驱动或数据库模式下仍可能执行多语句),或利用类型绕过(如传 10.5 触发隐式转换异常暴露信息)。
必须用驱动原生参数化支持:
- PostgreSQL +
psycopg2:cursor.execute("SELECT ... LIMIT %s OFFSET %s", (limit, offset)) - SQLite3:
cursor.execute("SELECT ... LIMIT ? OFFSET ?", (limit, offset)) - MySQL +
pymysql:cursor.execute("SELECT ... LIMIT %s OFFSET %s", (limit, offset)) - 禁止传字符串、浮点数或
None;limit和offset应先转int并做范围检查(如0 )
Django ORM 分页中 extra() 和 raw() 是注入盲区
Django 的 QuerySet 默认安全,但 .extra() 和 .raw() 会跳过 ORM 层,直连 SQL。若其中含用户输入,哪怕只拼一个字段名或排序方向,也立刻失守。
常见错误写法:
-
User.objects.extra(order_by=[f"{sort_field} {direction}"])——sort_field未白名单校验 -
User.objects.raw("SELECT * FROM auth_user ORDER BY %s", [sort_field])—— 占位符在ORDER BY子句中不生效(%s在字符串里,不是参数位置) -
User.objects.extra(where=["status = '%s'" % user_status])—— 字符串拼接,高危
安全替代方案:
- 排序改用
.order_by()+ 白名单字段(见第一部分) - 复杂查询改用
Q对象组合,或Case/When表达式 - 必须用
raw()时,所有动态值只能通过params=传入,且 SQL 字符串内不含任何f"{}"或%拼接
MyBatis 中 ${} 和 #{} 混用导致的分页漏洞
MyBatis 的 #{} 是预编译参数,安全;${} 是字符串替换,危险。分页场景中,开发者常误用 ${} 处理 ORDER BY 字段或 LIMIT 值,以为“只是变量”,结果引入注入。
典型错误配置:
-
ORDER BY ${sortField} ${sortOrder}—— 若sortField来自前端,可被替换为id; DROP TABLE-- -
LIMIT ${pageSize} OFFSET ${offset}—— 整数照样能触发语句截断或注释绕过
修复方式:
-
ORDER BY字段必须白名单校验后,用OGNL表达式静态选择:<if test="sortField == 'name'">ORDER BY name</if> -
LIMIT/OFFSET改用#{}(MyBatis 会自动处理整型绑定) - 禁用全局
${},或在 MyBatis 配置中设置safeRowBoundsEnabled=true(仅限较新版本)
分页看似简单,但 ORDER BY 字段名和 LIMIT/OFFSET 的处理逻辑完全不同:前者无法参数化,必须靠白名单;后者必须参数化,不能靠类型转换“蒙混过关”。这两类操作一旦混淆,漏洞就藏在最常规的代码路径里。

















