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数据进行查询的一个方法有三个主要的查询函数ArrayField
、JSONField
和HStoreField
,位于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下面下断点
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接口,主要功能为为