追洞小组 | 实战CVE-2020-7471漏洞
本文作者: 守拙
(Ms08067实验室追洞小组成员)
漏洞复现分析 认准追洞小组
一、漏洞名称:
通过
StringAgg
(分隔符)的潜在
SQL
注入漏洞
二、漏洞编号:
CVE-2020-7471
三、漏洞描述:
Django 1.11.28
之前的
1.11.x
、
2.2.10
之前的
2.2.x
和
3.0.3
之前的
3.0.x
版本
允许
SQL
注入,如果不受信任的数据用作
StringAgg
分隔符(例如,在存在多行数
据下载的
Django
应用程序中,使用用户指定的列分隔符进行下载的场景)。通过
向
contrib.postgres.aggregates.StringAgg
实例传递一个精心构造的分隔符,可
能会破坏转义并注入恶意
SQL
。
四、影响版本:
Django 1.11.x < 1.11.28
Django 2.2.x < 2.2.10
Django 3.0.x < 3.0.3
五、漏洞分析
聚合函数
StringAgg
的
delimiter
参数未经任何转义就嵌入到
sql
语句中,导致
sql
注入
六、实验环境及准备:
1.数据库:postgresql,版本无所谓,本文中使用kali虚拟机中自带的数据库,允许外部连接
-
修改
如下文件,监听所有端口
/etc/postgresql/12/main/postgresql.conf
-
修改如下
文件,允许外部连接
/etc/postgresql/12/main/pg_hba.conf
- 重启服务后,连接数据库并创建测试数据库
登陆:psql -U postgres -h [kali主机的IP]
创建测试数据库:CREATE DATABASE test,后面poc中会用到
其他postgresql语法可以参考菜鸟教程
2.POC: https://github.com/Saferman/CVE-2020-7471,运行环境django3.0.2
- 安装django3.0.2
pip installdjango==3.0.2
- 使用pycharm调试POC代码,参考POC中的readme文档
- 初始化数据库后可以用pgadmin连接看下,test数据库中应该有如下表
- Vul_app_info表中应该如下字段和数据
七、复现步骤:
POC测试脚本中有两个函数query()和query_with_evil(),前者用于模糊测试,后者用于注入点证明
1.
模糊测试
-
通过运行
query()
函数的异常捕获可以知道有两个特殊字符让程序产生了报
错(
%
和’)
-
将程序中异常捕获注释掉,
payload
使用
%
和’单独测试
- 通过报错可以看出分号没有转义导致sql语句报错,并直接在报错信息返回了拼接后的sql语句。将断点打在执行sql语句并产生报错的代码块去看完整的sql
- 得到程序运行的实际sql语句
SELECT "vul_app_info"."gender", STRING_AGG("vul_app_info"."name", \'\'\') AS "mydefinedname" FROM "vul_app_info" GROUP BY "vul_app_info"."gender" LIMIT 21
2.
注入点证明
- 通过注入sql语句使查询结果区别与程序原本的查询结果来证明注入点的可用
-
程序
原本执行的
sql
语句,最后是
limit 21
SELECT "vul_app_info"."gender", STRING_AGG("vul_app_info"."name", \'- \') AS "mydefinedname" FROM "vul_app_info" GROUP BY "vul_app_info"."gender" LIMIT 21
-
注入后的 sql 语句,最后是 limit 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 OFSET 1 --
- 运行结果验证
八、其他思考:
1. 漏洞利用场景
- Django 应用返回聚合数据的场景
- 用于聚合的字符用户可控
- 数据库得是 postgresql 数据库
- 好像不容易存在这种场景…
2. 漏洞挖掘思路
- 针对可能存在问题的函数,构建测试环境
- 针对该函数进行模糊测试,看是否有 sql 语句报错信息
- 如果模糊测试成功让 sql 语句报错,进行注入点利用验证
3. 漏洞修复
- 在django的git仓库的提交记录中可以看到django官方的修复方案
- https://github.com/django/django/commit/eb31d845323618d688ad429479c6dda973056136
-
新版本中将
delimiter
参数用
Value
函数处理了一下,再传递到
sql
中
-
升级
django
版本(
3.1.6
)再
debug
一下
-
分割符的地方用了
%s
,没有直接拼接进去,根据
Value
函数的注释说法是将参
数放到
sql
的参数列表中,最终以下面的方式执行,则不存在
sql
注入风险
sql="SELECT * FROM user_contacts WHERE username = %s"
user='zhugedali'
cursor.execute(sql,[user])
4.同类型函数
- 在postgresql数据库中和StringAgg函数一样可以传递分隔符参数的函数还有
array_to_string(array_agg(name),'-')
- 但是django中没有找到这个函数的API(没有提供或者是我太菜了没找到..)
扫描下方二维码加入星球学习
加入后会邀请你进入内部微信群,内部微信群永久有效!
目前35000+人已关注加入我们