漏洞簡(jiǎn)介
在Django 2.2 的 2.2.28 之前版本、3.2 的 3.2.13 之前版本和 4.0 的 4.0.4 之前版本中的 QuerySet.deexplain 中發(fā)現(xiàn)了SQL注入問(wèn)題。這是通過(guò)傳遞一個(gè)精心編制的字典(帶有字典擴(kuò)展)作為**options參數(shù)來(lái)實(shí)現(xiàn)的,并將注入負(fù)載放置在選項(xiàng)名稱中。
影響版本
2.2 =< Django < 2.2.28
3.2 =< Django < 3.2.13
4.0 =< Django < 4.0.4
環(huán)境搭建
創(chuàng)建存在 漏洞 Django 版本 3.2.12 項(xiàng)目
創(chuàng)建 startApp Demo 并依次修改文件
安裝 postgresql 數(shù)據(jù)庫(kù)
settings.py 設(shè)置連接數(shù)據(jù)庫(kù)為 postgresql 數(shù)據(jù)庫(kù)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'test',
'USER': 'postgres',
'PASSword': '123456',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
urls.py 設(shè)定對(duì)應(yīng)路由
fromdjango.contrib importadmin
fromdjango.urls importpath
fromDemo importviews
urlpatterns = [
path( 'admin/', admin.site.urls),
path( 'index/', views.index),
path( 'demo/', views.users),
path( 'initialize/', views.loadexampledata),
]
models.py
fromdjango.db importmodels
# Create your models here.
classUser(models.Model):
name = models.CharField(max_length= 200)
def__str__(self):
returnself.name
views.py
importjson
fromdjango.http importHttpResponse
fromdjango.shortcuts importrender
# Create your views here.
from.models importUser
defindex(request):
returnHttpResponse( 'hello world')
defusers(request):
query = request.GET.get( 'q')
query = json.loads(query)
qs = User.objects.get_queryset.explain(**query)
returnHttpResponse(qs)
defloadexampledata(request):
u = User(name= "Admin")
u.save
u = User(name= "Staff1")
u.save
u = User(name= "Staff12")
u.save
returnHttpResponse( "ok") 漏洞復(fù)現(xiàn) http:// 127.0.0.1: 8000/demo/?q={ "ANALYZE)+select+pg_sleep(5);--+": "aaa"}
發(fā)現(xiàn)成功構(gòu)造使得服務(wù)器沉睡
漏洞分析
在進(jìn)行代碼分析之前,我們先了解一個(gè)知識(shí)點(diǎn) EXPLAIN
EXPLAIN
EXPLAIN -- 顯示一個(gè)語(yǔ)句的執(zhí)行計(jì)劃
image
EXPLAIN [ ( option [, ...] ) ] statement
EXPLAIN [ ANALYZE ] [ VERBOSE ] statement
option:
ANALYZE [ boolean ] 執(zhí)行命令并顯示實(shí)際運(yùn)行時(shí)間
VERBOSE [ boolean ] 顯示規(guī)劃樹(shù)完整的內(nèi)部表現(xiàn)形式,而不僅是一個(gè)摘要
COSTS [ boolean ]
BUFFERS [ boolean ]
TIMING [ boolean ]
FORMAT { TEXT | XML | JSON | YAML }
statement:
查詢執(zhí)行計(jì)劃的 SQL 語(yǔ)句,可以是任何 select、insert、update、delete、values、execute、declare 語(yǔ)句
EXPLAIN ANALYZE不僅會(huì)顯示查詢計(jì)劃,還會(huì)實(shí)際運(yùn)行語(yǔ)句。EXPLAIN ANALYZE會(huì)丟掉任何來(lái)自SELECT語(yǔ)句的輸出,但是該語(yǔ)句中的其他操作會(huì)被執(zhí)行(例如INSERT、UPDATE或者DELETE)。
調(diào)試分析
django.db.models.query.QuerySet.explain
django.db.models.sql.query.Query.explain
django.db.models.sql.compiler.SQLCompiler.explain_query
django.db.models.sql.compiler.SQLCompiler.execute_sql
django.db.models.sql.compiler.SQLCompiler.as_sql
在這里會(huì)根據(jù)所選擇的數(shù)據(jù)庫(kù),來(lái)調(diào)用其相對(duì)應(yīng)的 explain_query_prefix 方法
django.db.backends.postgresql.operations.Databaseoperations.explain_query_prefix
postgresql 中 重寫(xiě)了 explain_query_prefix 方法將鍵名拼接到了 SQL 語(yǔ)句中
最后執(zhí)行的 SQL 語(yǔ)句是
'EXPLAIN (ANALYZE) SELECT PG_SLEEP(5);-- true) SELECT "Demo_user"."id", "Demo_user"."name" FROM "Demo_user"'
漏洞修復(fù)
做了一個(gè)過(guò)濾,發(fā)現(xiàn)危險(xiǎn)字符就拋出異常
只有字符串在白名單內(nèi)才會(huì)拼接到語(yǔ)句中