django raw()必须用params且只认%s:params须为列表或元组,仅绑定%s占位符,不支持{}或%(name)s;extra()的where中%s需配params,字典被忽略;rawsql中like需手动转义,结构参数如表名、字段名必须白名单校验。

raw() 必须用 params 且只认 %s
Django 的 raw() 方法完全跳过 ORM 编译器,SQL 字符串由你全权负责。它只对 %s 占位符做参数绑定,不支持 {} 或 %(name)s(MySQL/SQLite 不兼容)。params 必须是列表或元组,字典会被静默丢弃。
- ✅ 正确:
User.objects.raw("SELECT * FROM auth_user WHERE username = %s", params=["alice"]) - ❌ 危险:
User.objects.raw(f"SELECT * FROM auth_user WHERE username = '{u}'")—— 字符串拼接即失守 - ❌ 无效:
User.objects.raw("...", params={"username": u})——params不接受字典 - ❌ 错误顺序:
raw("WHERE age > %s AND name = %s", params=["bob"])—— 占位符数与参数数不匹配会报错或查错数据
extra() 的 where 参数不自动参数化
extra() 的 where 列表本身不解析参数,params 参数仅在顶层生效,且只作用于 where 字符串里的 %s。写成 extra(where=["status = %s"], params=[u]) 是唯一合法路径;其他形式全是裸 SQL。
- ✅ 唯一安全写法:
Article.objects.extra(where=["title LIKE %s"], params=[f"%{s}%"]) - ❌ 看似安全实则无效:
extra(where=["status = %s"], params={"status": u})—— 字典被忽略 - ❌ 直接拼接:
extra(where=[f"title = '{s}'"])—— 等同于注入入口 - ⚠️ 注意:
extra(select={...})、tables、order_by全部不校验、不转义,用户输入字段名或表名必须白名单控制
RawSQL 里 LIKE 模式要手动转义
RawSQL 只对 params 对应的占位符做绑定,不会处理字符串内通配符。漏掉转义,% 和 _ 就变成模糊匹配符号,可能返回爆炸性结果集或拖垮查询。
- ❌ 错误:
RawSQL(f"CASE WHEN title LIKE '%{search}%' THEN 1 ELSE 0 END") - ✅ 正确:
RawSQL("CASE WHEN title LIKE %s THEN 1 ELSE 0 END", params=[f"%{search}%"]) - ✅ 更稳妥:
Case(When(title__icontains=search, then=1), output_field=IntegerField())—— ORM 原生表达式自动处理转义和索引 - ⚠️ 若必须手写 LIKE:调用
connection.ops.prep_for_like_query(search)获取已转义字符串
用 connection.cursor() 时别漏掉 execute() 的 params
直接操作游标绕过所有 ORM 安全机制,execute() 的第二个参数必须是列表或元组,否则就是字符串拼接。
- ✅ 安全:
cursor.execute("SELECT * FROM user WHERE email = %s", [email]) - ❌ 危险:
cursor.execute(f"SELECT * FROM user WHERE email = '{email}'") - ❌ 多语句陷阱:
executescript()同样要求所有占位符统一用%s,且params长度必须匹配全部占位符总数 - ⚠️
fetchone()/fetchall()返回的是元组,字段顺序依赖 SQL 中SELECT顺序,别靠索引硬编码
原生 SQL 的安全边界非常窄:只要出现一次字符串拼接、一个未校验的字段名、一处漏转义的 LIKE 模式,就等于把数据库权限交出去。最常被忽略的是——Django 从不校验 SQL 结构部分,params 绑不住表名、排序字段、聚合函数名,这些必须靠白名单或 ORM 原生能力兜底。











