django orm 查询天然免疫sql注入,因其filter()、get()等方法底层使用参数化查询,用户输入仅作为绑定参数传递;但extra()、raw()等手动sql接口需严格用params传参,字段名等结构化输入须白名单校验。

Django ORM 查询天然免疫 SQL 注入
Django 的 QuerySet(比如 filter()、get()、exclude())在底层使用参数化查询,所有用户输入都作为绑定参数传递给数据库驱动,不会拼接进 SQL 字符串。这意味着只要你不手动拼接 SQL,就几乎不可能触发 SQL 注入。
典型安全写法:
# ✅ 安全:Django 自动转义并参数化
User.objects.filter(username=request.GET.get('q'))
<h1>❌ 危险:手拼 SQL,完全绕过 ORM 防护</h1><p>from django.db import connection
with connection.cursor() as cursor:
cursor.execute("SELECT * FROM auth_user WHERE username = '" + request.GET.get('q') + "'")
</p>
关键判断点:只要没出现 cursor.execute() 或 extra() 带原始 SQL 字符串,基本不用额外防注入。
extra() 和 raw() 是高危接口
这两个方法允许嵌入原始 SQL 片段或完整语句,一旦传入用户可控内容且未手动转义,就会立刻引入漏洞。
-
extra()的where、tables、params参数中,where若含%s占位符,必须配对使用params传参;直接拼字符串进where就是漏洞 -
raw()接收完整 SQL 字符串,绝不能用 f-string 或+拼接用户输入 - 即使用了
params,也要确认数据库驱动是否真正支持该参数化形式(PostgreSQL/SQLite/MySQL 都支持,但某些旧版 MySQLdb 可能有边界 case)
示例避坑:
# ❌ 错误:拼接导致注入
User.objects.extra(where=["username = '" + request.GET.get('u') + "'"])
<h1>✅ 正确:用 params 绑定</h1><p>User.objects.extra(where=["username = %s"], params=[request.GET.get('u')])</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2506" title="Python 3.14.2"><img
src="https://img.php.cn/upload/manual/001/221/864/6a696c31dfa4a111.webp" alt="Python 3.14.2" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2506" title="Python 3.14.2" class="overflowclass">Python 3.14.2</a>
<p class="overflowclass">Python 3.14.2是Python编程语言在2025年12月5日发布的稳定版本,属于3.14系列的第二个维护更新。该版本包含了18项修复,重点解决了多进程、数据类及正则表达式等模块的回归问题,并修复了CVE-2025-12084等安全漏洞。此版本标志着自由线程模式(移除GIL)正式获得官方支持,是Python发展的重要里程碑。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2506" title="Python 3.14.2" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><h1>✅ 更推荐:直接用 filter,语义清晰又安全</h1><p>User.objects.filter(username=request.GET.get('u'))
</p>
模板层 {{ }} 默认转义不等于 SQL 安全
有人误以为模板里用了 {{ user_input }} 就“全链路安全”,其实这是两回事:{{ }} 防的是 XSS,不是 SQL 注入。它不影响你代码里怎么查数据库。
常见混淆场景:
- 视图里用
raw()查数据,再把结果丢进模板 —— 模板转义拦不住查询时的注入 - 用
django.contrib.postgres.search的SearchVector时,若把用户输入直接塞进config参数(如config=request.GET['lang']),而该字段预期是固定枚举值(如'english'、'french'),就可能触发 SQL 层面的标识符注入(虽非典型 SQLi,但属服务端注入类风险)
这类场景建议白名单校验:
allowed_configs = {'en': 'english', 'fr': 'french', 'de': 'german'}
config_name = allowed_configs.get(request.GET.get('lang', 'en'), 'english')
SearchVector('body', config=config_name)
手动执行 SQL 时必须用 cursor.execute(sql, params)
当你确实需要写原生 SQL(比如复杂窗口函数、跨库 join),唯一安全方式是严格分离 SQL 结构和数据参数:
-
sql字符串里只能含固定结构,所有动态值必须用%s(MySQL/SQLite)或%(name)s(PostgreSQL)占位 -
params必须是 tuple 或 dict,不能是字符串或未经校验的任意对象 - 禁止对
params做字符串格式化(如"%s" % user_input),那等于白做 - 注意:表名、列名、ORDER BY 子句等**标识符无法参数化**,必须走白名单或正则校验(如
re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', col_name))
一个容易被忽略的细节:Django 的 connection.cursor() 返回的 cursor,其 execute() 方法行为依赖底层 DB-API 2.0 实现,不同数据库对参数类型容忍度不同(例如 PostgreSQL 对 None 转为 NULL 很稳,SQLite 可能在某些版本报错)。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










