【漏洞分析】CVE-2021-35042 Django SQL注入

admin 2021年12月22日01:54:07评论169 views字数 7611阅读25分22秒阅读模式

【漏洞分析】CVE-2021-35042 Django SQL注入

网安教育

培养网络安全人才

技术交流、学习咨询



该漏洞是由于对QuerySet.order_by()中用户提供数据的过滤不足,攻击者可利用该漏洞在未授权的情况下,构造恶意数据执行SQL注入攻击,最终造成服务器敏感信息泄露。

先本地创建一个Django环境,使用的版本为Django 3.1.10。具体的示例代码就使用:https://github.com/YouGina/CVE-2021-35042。


Order_by参数获取


其中获取GET参数值的是request.GET.get('order_by', 'name')这么一段,从order_by 中获取值,缺省为name。这个name的意思是数据库的字段。在models.py文件中有定义,也就是其实获取的是需要去查询的数据库字段名。


1class User(models.Model):
2    name = models.CharField(max_length=200)
3
4     def __str__(self):
5         return self.name


order_by这个参数的作用的排序,对一个列或者多个值进行升序或者降序的排列。比如:

1SELECT * FROM Websites ORDER BY alexa DESC;


上面这个SQL的意思就是,按照按照Alexa的顺序降序排列,DESC为降序,ASC为升序。

此问题按照官方的说法是:绕过标记为弃用的路径中的预期列引用验证。


流程分析


在这里我们先输入一个不存在的字段名name4,查看一下是怎样一个流程。首先进入如下函数,判断order_by 的排序顺序和表达式。

 1def add_ordering(self, *ordering):
2        """
3        Add items from the 'ordering' sequence to the query's "order by"
4        clause. These items are either field names (not column names) --
5        possibly with a direction prefix ('-' or '?') -- or OrderBy
6        expressions.
7  If 'ordering' is empty, clear all ordering from the query.
8         """

9        errors = []
10        for item in ordering:
11            if isinstance(item, str):
12                if '.' in item:
13                    warnings.warn(
14                        'Passing column raw column aliases to order_by() is '
15                        'deprecated. Wrap %r in a RawSQL expression before '
16                        'passing it to order_by().' % item,
17                        category=RemovedInDjango40Warning,
18                        stacklevel=3,
19                    )
20                    continue
21                if item == '?':
22                    continue
23                if item.startswith('-'):
24                    item = item[1:]
25                if item in self.annotations:
26                    continue
27                if self.extra and item in self.extra:
28                    continue
29                # names_to_path() validates the lookup. A descriptive
30                # FieldError will be raise if it's not.
31                self.names_to_path(item.split(LOOKUP_SEP), self.model._meta)
32            elif not hasattr(item, 'resolve_expression'):
33                errors.append(item)
34            if getattr(item, 'contains_aggregate'False):
35                raise FieldError(
36                    'Using an aggregate in order_by() without also including '
37                    'it in annotate() is not allowed: %s' % item
38                )
39        if errors:
40            raise FieldError('Invalid order_by arguments: %s' % errors)
41        if ordering:
42            self.order_by += ordering
43        else:
44            self.default_ordering = False


函数走到names_to_path的时候会根据传入的参数生成一个PathInfo 元组。返回最终的字段和没有找到的字段。其中opts代表模型选项,这里代表的这个表。然后去获取传入的字段值。当最后找不到这个字段的时候,会报一个Cannot resolve keyword '%s' into field的错误,也就是我们最后会看到的错误。

 1def names_to_path(self, names, opts, allow_many=True, fail_on_missing=False):
2    path, names_with_path = [], []
3    for pos, name in enumerate(names):
4        cur_names_with_path = (name, [])
5        if name == 'pk':
6            name = opts.pk.name
7
8   field = None
9        filtered_relation = None
10        try:
11            field = opts.get_field(name)
12        except FieldDoesNotExist:
13            if name in self.annotation_select:
14                field = self.annotation_select[name].output_field
15            elif name in self._filtered_relations and pos == 0:
16                filtered_relation = self._filtered_relations[name]
17                field = opts.get_field(filtered_relation.relation_name)
18        if field is not None:
19            # Fields that contain one-to-many relations with a generic
20            # model (like a GenericForeignKey) cannot generate reverse
21            # relations and therefore cannot be used for reverse querying.
22            if field.is_relation and not field.related_model:
23                raise FieldError(
24                    "Field %r does not generate an automatic reverse "
25                    "relation and therefore cannot be used for reverse "
26                    "querying. If it is a GenericForeignKey, consider "
27                    "adding a GenericRelation." % name
28                )
29            try:
30                model = field.model._meta.concrete_model
31            except AttributeError:
32                # QuerySet.annotate() may introduce fields that aren't
33                # attached to a model.
34                model = None
35        else:
36            # We didn't find the current field, so move position back
37            # one step.
38            pos -= 1
39            if pos == -1 or fail_on_missing:
40                available = sorted([
41                    *get_field_names_from_opts(opts),
42                    *self.annotation_select,
43                    *self._filtered_relations,
44                ])
45                raise FieldError("Cannot resolve keyword '%s' into field. "
46                                 "Choices are: %s" % (name, ", ".join(available)))
47            break


get_field函数的意思是返回一个字段名称的字段实例。对应的表内字段名和字段实例的字典类型。其中_forward_fields_map和fields_map的作用是相同的,就是后者还会检查一些内部的其他字段。

 1def get_field(self, field_name):
2    """
3    Return a field instance given the name of a forward or reverse field.
4    """

5    try:
6        # In order to avoid premature loading of the relation tree
7        # (expensive) we prefer checking if the field is a forward field.
8        return self._forward_fields_map[field_name]
9    except KeyError:
10        # If the app registry is not ready, reverse fields are
11        # unavailable, therefore we throw a FieldDoesNotExist exception.
12        if not self.apps.models_ready:
13            raise FieldDoesNotExist(
14                "%s has no field named '%s'. The app cache isn't ready yet, "
15                "so if this is an auto-created related field, it won't "
16                "be available yet." % (self.object_name, field_name)
17            )
18  try:
19        # Retrieve field instance by name from cached or just-computed
20        # field map.
21        return self.fields_map[field_name]
22    except KeyError:
23        raise FieldDoesNotExist("%s has no field named '%s'" % (self.object_name, field_name))


最后都不存在的情况下会告知,User has no field named name4。


当然如果是存在的字段,比如name,程序从get_field获取到的field就是cve_orderby.User.name。也就是不管传入的参数是否正常,只要走了names_to_path最后都会返回不存在字段或者存在的字段实例对象,而不是拼接SQL去执行,那么至少在这里就不能造成SQL注入了。整个执行的代码都为:SELECT "cve_orderby_user"."id", "cve_orderby_user"."name" FROM "cve_orderby_user"。


在查了一堆资料发现这个问题其实是绕过names_to_path这个判断,在函数add_ordering中,主要有五个判断:





字段中是否带点,带的话提示传入的是原始列的别名,并警告不建议这么使用。

字段是否为问号。

字段开头是否为短横杠。

判断是否在一个map字典里,暂时也不知道是干啥的。

判断是否有额外的参数信息。


所以,此处我们传一个带点的参数,比如name.name。到add_ordering中的时候,走到这个函数上,由于存在continue的作用,将跳过后续的判断,也就是不在进行names_to_path,无法获取字段的实例对象。

【漏洞分析】CVE-2021-35042 Django SQL注入

后续进入_fetch_all的时候就已经生成SQL:SELECT "cve_orderby_user"."id", "cve_orderby_user"."name" FROM "cve_orderby_user" ORDER BY ("name".name) ASC。也就是把参数name.name拼接进去。


于是构造一条语句,注意这里使用的是MySQL数据库。构造:SELECT cve_orderby_user.id, cve_orderby_user.name FROM cve_orderby_user ORDER BY (cve_orderby_user.name);select updatexml(1,concat(0x7e,(select @@version)),1);#) ASC


只需要传输参数:cve_orderby_user.name);select updatexml(1,concat(0x7e,(select @@version)),1);#

【漏洞分析】CVE-2021-35042 Django SQL注入


【漏洞分析】CVE-2021-35042 Django SQL注入

原文作者:Misaki

原文链接:https://misakikata.github.io/2021/08/CVE-2021-35042-Django-SQL%E6%B3%A8%E5%85%A5/

发表日期:August 6th 2021, 4:16:55 pm

更新日期:August 6th 2021, 4:19:06 pm

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

版权声明:著作权归作者所有。如有侵权请联系删除


开源聚合网安训练营

战疫期间,开源聚合网络安全基础班、实战班线上全面开启,学网络安全技术、升职加薪……有兴趣的可以加入开源聚合网安大家庭,一起学习、一起成长,考证书求职加分、升级加薪,有兴趣的可以咨询客服小姐姐哦!

【漏洞分析】CVE-2021-35042 Django SQL注入

加QQ(1005989737)找小姐姐私聊哦



精选文章


环境搭建
Python
学员专辑
信息收集
CNVD
安全求职
渗透实战
CVE
高薪揭秘
渗透测试工具
网络安全行业
神秘大礼包
基础教程
我们贴心备至
用户答疑
 QQ在线客服
加入社群
QQ+微信等着你

【漏洞分析】CVE-2021-35042 Django SQL注入


我就知道你“在看”
【漏洞分析】CVE-2021-35042 Django SQL注入

本文始发于微信公众号(开源聚合网络空间安全研究院):【漏洞分析】CVE-2021-35042 Django SQL注入

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月22日01:54:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【漏洞分析】CVE-2021-35042 Django SQL注入https://cn-sec.com/archives/460754.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息