Django JSONField/HstoreField SQL注入(CVE-2019-14234)
在逛p神的小密圈的时候发现一篇关于Django的sql注入问题,于是尝试着复现一波
受影响版本:
Django 2.2.x < 2.2.4
Django 2.1.x < 2.1.11
Django 1.11x < 1.11.23
官方公告:https://www.djangoproject.com/weblog/2019/aug/01/security-releases/
环境准备
vulhub上面已经有相应的docker镜像了
https://github.com/vulhub/vulhub/tree/7ed1b98faa901a3bcbb756935cf69e13e0d87460/django/CVE-2019-14234
把整个vulhub项目可以下载下来
git clone https://github.com/vulhub/vulhub.git cd vulhub/django/CVE-2019-14234/ docker-compose build docker-compose up -d
之后可以看下项目的README.md
,就可以验证漏洞的存在性了
当然在docker里面啥不好看代码,也可以本地直接搭Django服务
Django本地搭建
在IDEA中创建个Django项目,打开已安装的包,发现Django版本已经是2.4,没有该漏洞了,于是修改一下,这里因为python2 是不支持Django,2.x版本的,所以这里用的python3,其实IDEA中venv很好配置
这张图是在第一次在python2下安装2.3失败时截断(凑合着用)
Postgresql安装
首先下载
apt-get install postgresql
之后会在系统下自动生成一个postgersql
的账号
postgersql
账号是用来登录连接数据库的,因为postgersql
是没法用root来连接的
postgersql
开放端口在5432
切换账号,进入数据库
su postgres psql
设置下密码,有2种方法
方法1 alter user postgres with password ‘postgres‘; //密码改为postgres 方法2 /password postgres Enter new password: xxx //这会是提示 Enter it again: xxx
创建库
create database djangosql; \c djangodb; //相当于mysql的 use djangodb
创建表,要加_
,我直接用user
当库名报错
create table t_user(id integer, score jsonb);
添加数据
insert into t_user values (1,‘{"username":"sijidou","age":90}‘::jsonb); insert into t_user values (2,‘{"username":"siji","age":45}‘::jsonb);
其他语法和mysql
大差不差的,postersql
还可以加json数据
\l //查库 \dt //查表 \q //退出
配置
之后的流程是创建 startapp
python manager.py startapp SqliTest
编写下SqliTest\models.py
,并创建类中中有支持json的类型
from django.db import models from django.contrib.postgres.fields import JSONField # Create your models here. class test(models.Model): name = models.CharField(max_length=255) text = JSONField() def __str__(self): return self.name
修改下Setting.py
迁移数据库
python manage.py migrate python manage.py makemigrations
导入数据库
python manage.py loaddata test.json
这里导入的我使用的是vulhub上改的测试数据,注意下model要对应 app/models.py
中的类, fields
域中的键要和数据库的字段名相同
[ { "model": "DjangoSql.test", "pk": 1, "fields": { "name": "a1", "text": { "title": "title 1", "author": "vulhub", "tags": ["python", "django"], "content": "..." } } }, { "model": "DjangoSql.test", "pk": 2, "fields": { "name": "a2", "text": { "title": "title 2", "author": "vulhub", "tags": ["python"], "content": "..." } } } ]
数据就成功的进来了
预备知识
python中的Django框架也好flask框架也罢,都会推荐使用Postgresql数据库,一般连接会用这3个函数JSONField
,ArrayField
,HStoreField
来处理json数据的连接
这里跟进下JSONField()
来看
在使用filter()
过滤查询的时候会触发到get_transform()
,如果不满足父类Field()
的get_transform()
,则会调用KeyTransformFactory()
至于为什么数据库查询语句的filter()
会触发get_transform()
,在官方文档被拿来举了个例子
那么为什么不满足,这里盗用下p神的理解
在JSONField中,lookup实际上是没有变的,但是transform从“在外键表中查找”,变成了“在JSON对象中查找”,所以自然需要重写get_transform函数。
继续跟进KeyTransformFactory()
这个工厂类,可以看到被__call__
的时候返回了KeyTransform()
继续查看,KeyTransform()
里面的as_sql()
方法,也就是要查询数据库的方法
最主要的部分在最后返回值的时候把lookup和前面内容直接进行了拼接,这也是漏洞形式原因
漏洞利用
先启动admin后台管理(后台管理主要功能可以管理数据库的),把我们有json数据的数据添加注册一下
from django.contrib import admin from .models import test # Register your models here. admin.site.register(test)
看看正常的查询语句,它是根据text
字段的title
键的内容进行查找的(这里有2个_
)
debug跟踪下流程
首先进入get_transform()
中的KeyTransformFactory()
中
接下来按照预期进入了KeyTransform()
中,之后继续进入
接下来就进入了as_sql()
的函数中
上面的逻辑变化暂时不要管(咱也没接触过这么复杂的框架),运行到最下面的时候,来看看几个参数的值
params,不用管这里是将lhs
,lookup
,params
进行拼接,先把他们的写在一起,在前一步,把他们赋值到tmp中可以看到tmp的结果
大致就是这样的
filter("DjangoSql_test.text->title")
再一步可能就是这样
select * from DjangoSql_text where (DjangoSql_text.text->>‘title‘) = xxx
这里的‘title‘
中加入‘
,就能造成sql注入,在这里是被转义了的。但是之后的操作会去掉\,(不然postersql的语句不正确)
发送过去,sql语句报错了
尝试利用下
text__title‘ = ‘"a"‘) and 7778=CAST((SELECT version())::text AS NUMERIC)-- //编码下特殊的字符 text__title%27+%3d+%27%22a%22%27)%20and%207778%3dCAST((SELECT%20version())::text%20AS%20NUMERIC)--
成功返回版本
参考链接
https://www.tuicool.com/articles/ymyU7zF
https://www.leavesongs.com/PENETRATION/django-jsonfield-cve-2019-14234.html