django orm 能防 sql 注入是因为其通过参数化查询绑定用户输入的值,而非拼接字符串;但字段名、表名等结构元素需手动白名单校验,否则仍存在风险。

直接用 Django ORM 就能防 SQL 注入,前提是别绕过它去拼接字符串。只要你坚持用 filter()、get()、exclude() 这类方法传参,Django 会在底层自动做参数化处理——用户输入永远不会被当作 SQL 代码执行。
为什么 ORM 能天然防注入
Django ORM 不是把 Python 表达式转成 SQL 字符串再执行,而是构建查询表达式树,最后由数据库驱动统一绑定参数。这意味着 username 字段的值永远走的是 WHERE username = %s + 参数元组的方式,和手写 cursor.execute("SELECT ... WHERE name = %s", [name]) 是同一套安全机制。
常见错误现象:有人看到 User.objects.filter(username=request.GET.get('u')) 觉得“好像没做验证”,其实完全没问题;但一旦改成 raw("SELECT * FROM auth_user WHERE username = '{}'".format(u)),就立刻掉进坑里。
- ORM 方法(如
filter())只接受字段名作为关键字参数名,值一律走参数绑定 - 字段名本身不能动态传入(比如
filter(**{field_name: value})),否则需额外校验字段白名单 - 像
order_by()、values_list()这类接收字段名字符串的方法,不涉及用户值代入,但字段名若来自请求,仍要限制范围
哪些 ORM 写法看似安全实则危险
不是所有带 objects 的调用都免疫注入。以下操作会绕过 ORM 的参数化保护:
-
extra()中的tables、where、params若混入未过滤的用户输入(如extra(where=["status = '{}'".format(user_input)])) -
raw()查询中用%或.format()拼接 SQL,哪怕只拼字段名或表名 -
annotate()或aggregate()里用Func()构造表达式时,把用户输入直接塞进template字符串
例如这个管理器写法就是高危的:.extra(select={'score': "COALESCE({}, 0)".format(request.GET.get('col'))})——col 值若为 id); DROP TABLE auth_user; --,就完了。
必须手动校验的边界场景
ORM 只保护“值”,不保护“结构”。当你需要动态决定查哪个字段、按什么排序、用什么聚合函数时,就得自己把关:
- 对
request.GET.get('sort')做白名单检查:if sort not in ['id', '-name', 'email']:才允许传给order_by(sort) - 用
getattr(MyModel, field_name, None)确认字段真实存在且可查询,再用于values(field_name) - 避免在
Q()对象中拼接字符串条件,改用字典解包:Q(**{field_name + '__icontains': keyword}),前提是field_name已校验
性能影响几乎为零,但漏掉校验字段名,等于在防火墙上凿了个洞——ORM 的防护层就失效了。
真正容易被忽略的点是:ORM 防得住值,防不住字段名、表名、函数名这些 SQL 结构元素。只要用户输入参与了 SQL 的“骨架”生成,就必须手动白名单或映射,而不是依赖 ORM 自动兜底。











