django 中 raw()、cursor.execute() 等原生 sql 操作必须严格使用 %s 占位符与 params 参数化传参,禁止字符串拼接;extra() 的 where 不支持 params,rawsql 中 like 通配符需手动转义,结构参数须白名单校验。

raw() 必须用 params= 且只认 %s
Django 的 raw() 不校验 SQL 字符串,拼接即失守。哪怕只写 f"WHERE name = '{name}'",攻击者传 admin' OR 1=1 -- 就能绕过认证。
正确做法是把用户输入全交给 params,且必须用位置占位符 %s:
-
params只接受列表或元组,不能是字典(除非后端明确支持命名参数,如 PostgreSQL 的%(name)s;MySQL/SQLite 不认) - SQL 中
%s出现几次,params就得有几个值,顺序错一位就查错数据 -
raw()查询必须包含主键字段,否则 ORM 实例化失败
cursor.execute() 同样要参数化,别漏掉 with 语句
绕过 ORM 直接用 connection.cursor() 时,安全逻辑完全由你负责。常见错误是写成 cursor.execute(f"SELECT * FROM user WHERE id = {user_id}") —— 这和裸写 SQL 没区别。
必须用 %s 占位 + params 传参,并养成 with 包裹习惯:
-
with connection.cursor() as cursor:确保游标自动关闭,避免连接泄漏 cursor.execute("UPDATE user SET status = %s WHERE id = %s", [new_status, user_id])- 查询结果用
cursor.fetchall()或cursor.fetchone(),不建议长期持有游标
extra() 的 where 参数不认 params,别被文档误导
extra() 是个陷阱区:它的 where 列表看似支持 params,但实际被 Django 忽略。写 extra(where=["status = %s"], params=[u]) 完全无效,%s 会被当字面量处理。
只要 where 字符串里含用户输入,就是注入漏洞。正确路径只有两条:
- 改用
Q对象组合条件:filter(Q(status__exact=u) | Q(type__icontains=u)) - 彻底放弃
extra(),改用raw()或cursor.execute()并严格参数化
RawSQL 和 annotate() 混用时,LIKE 通配符必须手动转义
RawSQL 表达式不自动参数化整个字符串,只对 params 对应的 %s 占位符生效。漏掉转义,% 和 _ 就会变成通配符,导致查出不该查的数据,甚至拖垮数据库。
比如搜索关键词 hello%,直接塞进 SQL 会匹配所有以 hello 开头的记录:
- 错误:
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 原生表达式自动处理转义、索引优化和数据库兼容性
结构参数永远不进参数化:字段名、表名、ORDER BY、函数名这些,params 绑不住。一旦来源不可信,必须走白名单校验,别指望 ORM 替你兜底。











