Django 是一个开源的 Web 应用程序框架,使用 Python 编写,基于 MVC(Model - View - Controller)模型构建。它最初是为了管理由 Lawrence 出版集团拥有的新闻内容网站而构建的,是一个 CMS(内容管理系统)软件。
Django 3.1.x -> 3.1.13 和 3.2.x -> 3.2.5 版本存在 SQL 注入漏洞。
导致此漏洞的原因是 QuerySet.order_by() 中对用户可控输入数据的过滤功能不足以防御 SQL 注入攻击。该漏洞可能被利用,允许攻击者执行未经授权的操作,从而导致敏感数据泄露。
| CVE - ID | CVE-2021-35042 |
|---|---|
| 严重性 | 9.8 - 严重 |
| CWE - ID | CWE-89: SQL命令中特殊元素的不当中和('SQL注入') |
| 漏洞发布日期 | 2021年7月1日 |
| 受影响软件 | 3.1.x < 3.1.13, 3.2.x < 3.2.5 |
| 是否需要认证 | 不需要 |
在 Django 中,创建数据库表和定义字段是通过在 models.py 文件中声明一个模型类来完成的。在此示例中,我们声明了一个名为 Wolf 的表,以及一个名为 name 的字段。


Django 内置的 ORM 框架用于操作数据库,查询结果是一个集合,这个集合就是一个 QuerySet。
order_by(fields)
默认情况下,order_by() 返回一个 QuerySet,该 QuerySet 按照模型 Meta 中 ordering 选项指定的顺序进行排序。我们可以通过使用 order_by() 方法在每个查询中覆盖 order_by 条件。
示例
wolves = Wolf.objects.order_by('-name', 'id')
上述查询的结果将按 name 字段降序排序,然后按 id 字段升序排序。字段名 name 前面的负号表示结果按降序排序。
以下示例将根据用户输入的字段对返回结果进行排序,如果没有传入任何值,则按
id字段排序。
结果

在 3.1 和 3.2 版本中,Django 允许将查询方法与表名结合到 order_by 查询中。这也是导致此漏洞的主要原因。
传入表名与正常传入字段名得到的结果相同
cve202135042_wolf 是表名
首先,应用程序直接调用 order_by() 函数,处理 order_by() 函数的代码定义在:
django/db/models/query.py

order_by() 函数执行两件事
- 清除当前所有由 order_by() 调用的方法,并在 order_by 接收到不同值时清除传入的默认参数。
- 将参数传递给 order_by。
add_ordering()函数将执行此操作。
def add_ordering(self, *ordering):
"""
Add items from the 'ordering' sequence to the query's "order by"
clause. These items are either field names (not column names) --
possibly with a direction prefix ('-' or '?') -- or OrderBy
expressions.
If 'ordering' is empty, clear all ordering from the query.
"""
errors = []
for item in ordering:
if isinstance(item, str):
if '.' in item:
warnings.warn(
'Passing column raw column aliases to order_by() is '
'deprecated. Wrap %r in a RawSQL expression before '
'passing it to order_by().' % item,
category=RemovedInDjango40Warning,
stacklevel=3,
)
continue
if item == '?':
continue
if item.startswith('-'):
item = item[1:]
if item in self.annotations:
continue
if self.extra and item in self.extra:
continue
# names_to_path() validates the lookup. A descriptive
# FieldError will be raise if it's not.
self.names_to_path(item.split(LOOKUP_SEP), self.model._meta)
elif not hasattr(item, 'resolve_expression'):
errors.append(item)
if getattr(item, 'contains_aggregate', False):
raise FieldError(
'Using an aggregate in order_by() without also including '
'it in annotate() is not allowed: %s' % item
)
if errors:
raise FieldError('Invalid order_by arguments: %s' % errors)
if ordering:
self.order_by += ordering
else:
self.default_ordering = False
传递给 add_ordering() 的参数是一个数组。
例如,当参数按如下方式传递时:
wolves = Wolf.objects.order_by( 'name' , 'id' )此时,应用程序将转换为数据库中的查询语句,如下所示:
SELECT "cve202135042_wolf"."id", "cve202135042_wolf"."name" FROM "cve202135042_wolf" ORDER BY "cve202135042_wolf"."name" ASC, "cve202135042_wolf"."id" ASC
传入时,add_ordering 函数将检查数组中的每个元素,如果它是一个 string,将检查以下 5 种情况:
if '.' in item:它将检查这是否是一个带有列名的查询,并且该列在 SQL 语句中是否指定了表名。如果是,则会发出警告并continue。if item == '?':如果元素的值是 '?',则输出结果将随机排序,continue。if item.startswith('-'):如果 item 以 '-' 字符开头,则查询结果将按 DESC(降序)排序。if item in self.annotations:它检查是否包含注释,如果包含,则continue。if self.extra and item in self.extra:确定是否有额外补充,如果有则continue。
经过 5 次检查后,参数继续传递给 self.names_to_path(item.split(LOOKUP_SEP), self.model._meta) 函数,以进一步检查它是否是一个有效的列名,如果有效,则将其添加到 Query 类的 self.ordering 中以进行进一步处理。
Django 的 ORM 对插入查询的数据进行了非常严格的过滤,但这次导致 SQL 注入的源代码更改是因为作者假设如果列名是 UUID(通用唯一标识符)列,则 order_by 查询将无法执行。
这意味着如果传入的数据是 xxx-xxx-xxx-xxx(UUID 格式),则查询将无法执行。
更改前的代码
# django/db/models/sql/constants.py
ORDER_PATTERN = _lazy_re_compile ( r '\?|[-+]?[.\w]+$' )
# django/db/models/sql/query.py
def add_ordering ( self , * ordering ):
errors = []
for item in ordering :
if isinstance ( item , str ) and ORDER_PATTERN . match ( item ):
if '.' in item :
warnings . warn (
'Passing column raw column aliases to order_by() is '
'deprecated. Wrap %r in a RawSQL expression before '
'passing it to order_by().' % item ,
category = RemovedInDjango40Warning ,
stacklevel = 3 ,
)
elif not hasattr ( item , 'resolve_expression' ):
errors . append ( item )
if getattr ( item , 'contains_aggregate' , False ):
raise FieldError (
'Using an aggregate in order_by() without also including '
'it in annotate() is not allowed: %s ' % item
)
if errors :
raise FieldError ( 'Invalid order_by arguments: %s ' % errors )
if ordering :
self . order_by += ordering
else :
self . default_ordering = False
从上面的代码可以看出,如果参数匹配 ? 或以 - 开头且后跟常规字符或 .,则查询才会执行。
因此,当列名是 UUID 时,它将是一个无效值,无法传入 order_by。
此处理代码的更改已被接受,更改如下:
https://github.com/charettes/django/commit/513948735b799239f3ef8c89397592445e1a0cd5

它使用了 self.name_to_path 函数来验证输入数据。