CVE-2019-14234 Django JSON SQL注入 分析
2019-08-08 07:41:00 Author: xz.aliyun.com(查看原文) 阅读量:100 收藏

8月1号,DJango官方发出更新,其中修复了一个存在于JSON的SQL注入漏洞(CVE-2019-14234)

作为以Django为主要开发的人来说,需要好好研究一下。

首先来看看官方是如何修复的

首先将django/contrib/postgres/fields/hstore.py文件里面的KeyTransform类的as_sql函数中的直接传递字符传改为了将self.key_name单独使用数组进行传递,其中%%的意思为"转换说明符",其主要作用为直接转化为单个"%"符号而不需要参数。类似于\\\

In[1]:"%%"%()
Out[1]: '%'

# 具体使用方法如下

In [2]: '%s %%s'%'test'
Out[2]: 'test %s'

之后在django/contrib/postgres/fields/jsonb.py文件中将对self.key_name变量的返回统一改成了使用数组进行转换。并且后期在单元测试中加入了对JSONField的SQL注入测试

因此,也发现了通过JSON进行SQL注入的payload

在github上,官方也给出了具体的原因

CVE-2019-14234: SQL injection possibility in key and index lookups for ``JSONField``/``HStoreField``
====================================================================================================

:lookup:`Key and index lookups <jsonfield.key>` for
:class:`~django.contrib.postgres.fields.JSONField` and :lookup:`key lookups
<hstorefield.key>` for :class:`~django.contrib.postgres.fields.HStoreField`
were subject to SQL injection, using a suitably crafted dictionary, with
dictionary expansion, as the ``**kwargs`` passed to ``QuerySet.filter()``.

其通过**kwargs传递键值树来绕过了QuerySet.filter()方法,PostgreSQL的使用json数据进行查询的一个方法有三个主要的查询函数ArrayFieldJSONFieldHStoreField,位于django.contrib.postgres.fields路径下面,出问题的是JSONField方法和HStoreField方法,两种方法都为内置的JSONB存储,这两种方法的区别为JSONField类似于HStoreField,并且可以使用大型字典执行得更好。它还支持除字符串以外的类型,例如整数,布尔和嵌套字典。

首先models里面的数据库格式为

from django.db import models
from django.contrib.postgres.fields import JSONField

# Create your models here.

class Json(models.Model):
    name = models.CharField(max_length=200)
    data = JSONField()

    def __str__(self):
        return self.name

使用python manage.py shell打开shell窗口,案例使用JSONField函数进行存储和查询

Json方式查询的方法可以使用语句

Json.objects.filter(data__breed='test')    # 查询data数据下名称为breed的内容为'test'的整个字段

或者使用语句

Json.objects.filter(**{"data__breed":'test'})

也可以达到要求

因HStoreField方法预与之相同,故不做赘述。
因此既能够使用官方给的payload进行验证

Json.objects.filter(**{"""data__breed'='"a"') OR 1=1 OR('d""":'x',})


注入成功!!

查询语句转换为SQL语句为

select * from app01_json where (data- >'breed' ? 'test');

所以产生注入的语句为

select * from app01_json where (data- >'breed' ? 'a') OR 1=1 OR (data->'a'?'x');

为了探究该漏洞在哪一块业务中显现,因此来分析一下。同时又看到官方对过滤的参数都是self.key_name,因此将跟进这一个参数,发现该参数是由KeyTransform类中传入的参数得来的,再跟着KeyTransform走,发现类KeyTransformFactory调用KeyTransform了并且向里面传入了self.key_name

接着跟进,同时在JSONField类中的get_transform方法传入了self.key_name参数

同时跟进super中的父类函数,其传参来自于RegisterLookupMixin类中的lookup_name的变量,其JSONField和HStoreField都来自于这个类方法

class RegisterLookupMixin:

    @classmethod
    def _get_lookup(cls, lookup_name):
        return cls.get_lookups().get(lookup_name, None)

    @classmethod
    @functools.lru_cache(maxsize=None)
    def get_lookups(cls):
        class_lookups = [parent.__dict__.get('class_lookups', {}) for parent in inspect.getmro(cls)]
        return cls.merge_dicts(class_lookups)

    def get_lookup(self, lookup_name):
        from django.db.models.lookups import Lookup
        found = self._get_lookup(lookup_name)
        if found is None and hasattr(self, 'output_field'):
            return self.output_field.get_lookup(lookup_name)
        if found is not None and not issubclass(found, Lookup):
            return None
        return found

    def get_transform(self, lookup_name):
        from django.db.models.lookups import Transform
        found = self._get_lookup(lookup_name)
        if found is None and hasattr(self, 'output_field'):
            return self.output_field.get_transform(lookup_name)
        if found is not None and not issubclass(found, Transform):
            return None
        return found

那么,首先在as_sql函数和get_transform下面下断点

和as_sql方法

在views.py中写一个逻辑函数

def query(request):
    m = Json.objects.filter(data__breed="test")
    return HttpResponse("OK")

访问http://127.0.0.1/query/,提取断点信息,发现lookup_name传入的参数为breed

之后跳入"(%s %s %s)" % (lhs, self.operator, lookup), params的语句为("app01_json"."data" -> 'breed') [],因此可以下结论我们需要控制的参数为breed,即使用**{"data__breed":"test"}可以对传进去的breed参数进行恶意构造从而达到SQL注入,self.operator的值为字符串->

进而出现查询结果,我们将SQL注入语句插入之后显现的语句为("app01_json"."data" -> 'breed'='"a"') OR 1=1 OR('d') []

最终显示注入成功,官方对RegisterLookupMixin的定义为查询的API接口,主要功能为为


文章来源: http://xz.aliyun.com/t/5896
如有侵权请联系:admin#unsafe.sh