bigywater.tech
Django · 125

SQL Injection 与 ORM 参数化查询

ORM 通常是安全的,但 Django 也提供了 raw()、cursor、extra() 这些让你自己写 SQL 的口子。这篇在自己的本地表上用同一个输入,把安全写法与危险写法的结果并排摆出来。

0 行 vs 3 行
同一个输入 x' OR '1'='1:参数化 vs f-string 拼接
%s 不能当列名
参数是值;动态排序要用白名单
order_by(用户输入)
不注入,但可以按任意字段排序

ORM 怎么发 SQL

实测 · 本机执行输出
  发给数据库的 SQL : WHERE "lab_person"."name" = %s
  参数(单独传递): ("x' OR '1'='1",)
  返回行数: 0 (没有叫这个名字的人)

用 connection.execute_wrapper 截下来的 SQL 里是 %s 占位符,参数在第二个位置单独传递——这就是 ORM 防注入的原理。

自己写 SQL:拼接 vs 参数

实测 · 本机执行输出
  f-string 拼接  → 返回 3 行(全部): ['alice', 'bob', 'carol']
  参数化 %s      → 返回 0 行

拼接时输入里的单引号结束了字符串、OR '1'='1' 变成永真条件。这里的数据是我自己建的 3 行本地数据,只演示「条件被改写」,没有做任何破坏性操作。

cursor 与参数的限制

实测 · 本机执行输出
  cursor f-string  → 3 行
  cursor 参数化    → 0 行
  ORDER BY name DESC(列名写在 SQL 里)       → ['carol', 'bob', 'alice']
  ORDER BY %s DESC,参数 'name'(当作字符串) → ['alice', 'bob', 'carol'] ← 与插入顺序相同,没有按 name 排序:参数是值,不是列名

order_by 与字段泄漏

实测 · 本机执行输出
  order_by('name'                          ) → ['alice', 'bob', 'carol']
  order_by('-name'                         ) → ['carol', 'bob', 'alice']
  order_by('secret'                        ) → ['bob', 'carol', 'alice']
  order_by('name; DROP TABLE lab_person'   ) → FieldError: Cannot resolve keyword 'name; DROP TABLE lab_person' into field. Choic
  order_by('nope'                          ) → FieldError: Cannot resolve keyword 'nope' into field. Choices are: id, name, secre
  白名单之后 safe_order("secret") = name

这不是 SQL 注入(Django 会校验字段名),但如果排序参数直接来自请求,用户可以通过排序结果推断出你没想暴露的字段(比如 secret 列的大小关系)。

extra() 里的拼接

实测 · 本机执行输出
  extra(where=[f-string])  → 3 行
  extra(where, params)     → 0 行

extra() 和 RawSQL 一样,是「你负责安全」的接口;能用 ORM 表达的查询就不要用它们。

分步演示

SQL 注入
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite;表 lab_person 三行本地数据(name + secret);SQL 用 execute_wrapper 记录
    • 没有验证:其它数据库驱动的参数绑定差异、JSONField / 注解 / 函数参数里的注入面(Django 历史上有过相关安全公告,本文没有复现)、存储过程、二阶注入
    京ICP备2026067502号-1京公网安备11010502063487号