ORM本身不自动防注入,仅标准查询方法(如filter、where)默认参数化;raw()、text()、${}等原生接口及动态字段名(如filter(**{field: value}))会绕过防护,表名、排序字段等结构部分须白名单校验。

不能。ORM框架本身不自动防注入,它只在你严格按安全方式使用时才起作用;一旦调用 raw()、text()、DB::raw() 或误用 ${},参数化机制立即失效。
哪些 ORM 接口会直接绕过参数化
主流 ORM 的标准查询方法(如 filter()、where()、find())默认走预编译 + 参数绑定,天然防注入。但以下接口完全跳过 ORM 查询编译器,等同于直连数据库驱动:
-
raw()(Django)、text()(SQLAlchemy)、DB::raw()(Laravel)、${}(MyBatis XML)——只要 SQL 字符串里有用户输入拼接,就失效 -
extra()(Django)和annotate()中混用RawSQL或字符串模板(如template="%(function)s(, %(decimals)s)"),decimals若为恶意字符串,会直接进 SQL -
cursor.execute()(原生 DB-API 调用)未用占位符,而是用f"SELECT * FROM {table}"拼表名
为什么表名、字段名、ORDER BY 无法参数化
数据库协议只允许「值」参与参数化(即 WHERE name = ?、INSERT INTO VALUES (?)),而「结构」部分(表名、列名、ORDER BY、GROUP BY、LIMIT offset)在 SQL 解析阶段就必须确定,驱动层根本不支持运行时替换。
-
ORDER BY {user_input}即使其他所有参数都安全,整条语句仍可被劫持 -
SELECT * FROM {table_name}无法靠params=补救,必须白名单校验或配置映射 - 典型错误:把
request.GET.get("sort")直接传给order_by(),却没做if sort not in ["created_at", "score"]:拦截
动态字段名(如 filter(**{field: value}))为何危险
Django 的 filter() 支持字典解包,但键(field)不会被转义或校验。攻击者可传入:
-
__class__、__dict__等模型元属性,触发非预期行为 -
id__in后接子查询字符串(如"id__in=(SELECT password FROM auth_user)"),若后端解析宽松可能执行 - 任意嵌套字段路径(如
profile__user__is_staff),若字段未定义或权限未控,可能暴露数据或引发异常
安全做法不是禁用字典解包,而是先白名单过滤 field,例如:if field not in ["title", "author__name", "status"]: 再构造 {field: value}。
真正难的从来不是拼 SQL,而是搞清“谁能在什么条件下看到什么数据”。ORM 帮你挡住了最野的攻击,但业务规则得自己守;最容易被忽略的,是那些不落在 WHERE 里的注入点——比如 jsonb_path_query 的路径参数、extra(tables=["user"]) 引入的额外表、甚至 model._meta.db_table 拼接的日志表名。








