Django是一个用Python编写的开源Web应用框架,基于MVC(模型-视图-控制器)模式构建。最初它是为了管理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: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') |
| 漏洞发布时间 | 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函数来验证输入数据。
但是,在检查如果item中包含.后,会将之视为带表名的查询,并执行continue语句,导致直接跳过使用self.name_to_path函数来验证数据合法性。
在get_order_by函数中处理点号.的代码如下:
django/db/models/sql/compiler.py
if '.' in field :
table , col = col . split ( '.' , 1 )
order_by . append ((
OrderBy (
RawSQL ( ' %s . %s ' % (
self . quote_name_unless_alias ( table ), col ), [ ]),
descending = descending
), False ))
continue
函数self.quote_name_unless_alias处理表名,过滤合法的表名,而忽略了对列名的过滤,因此我们可以注入SQL语句。
在当前Django 4.0版本中,通过点号.查询表名的方式已被移除,不再支持。补丁针对3.1和3.2版本发布。受影响的版本为3.2至3.2.4以及3.1至3.1.12。
3.2.x Fixed CVE-2021-35042 -- Prevented SQL injection in QuerySet.o…
3.1.x Fixed CVE-2021-35042 -- Prevented SQL injection in QuerySet.o…
修复非常简单,重新引入了使用正则表达式检查数据的方式。

将Django更新到不受影响的版本。
Docker & Docker-compose
git clone https://github.com/LUUANHDUC/CVE-2021-35042.git./setup.sh进行初始设置sudo docker-compose up --buildsudo docker exec -it cve-2021-35042_web_1 python manage.py makemigrations cve202135042sudo docker exec -it cve-2021-35042_web_1 python manage.py migratehttp://localhost:8000/wolves/?order_by=name安装完成后的界面

条件: 为了能够利用,我们必须以某种方式知道表名 :))
在注入语句时,我们必须知道表名才能执行SQLi语句。
当输入错误的表名时

当输入正确的表名时,orderby查询正常执行。

此时的语句将变成
SELECT "cve202135042_wolf"."id", "cve202135042_wolf"."name" FROM "cve202135042_wolf" ORDER BY ("cve202135042_wolf"."name") ASC
此时,我们可以结束前面的order_by语句并注入一条SQL语句进行利用。

SELECT "cve202135042_wolf"."id", "cve202135042_wolf"."name" FROM "cve202135042_wolf" ORDER BY ("cve202135042_wolf"."name"); SELECT * from cve202135042_wolf where id =1; --) ASC
https://www.djangoproject.com/weblog/2021/jul/01/security-releases/ https://xz.aliyun.com/t/9834 https://www.bugxss.com/vulnerability-report/3095.html https://blankheart.top/2022/04/07/cve-2021-35042/ https://itcn.blog/p/1648921763575859.html https://github.com/YouGina/CVE-2021-35042