一次安全測試引發的對Django框架文件上傳安全機制的初步分析

0x00. 起因

我司的堡壘機是基於jumpserver 0.3版本進行二次開發,進行了大量的重構和新功能的添加,基本滿足了公司安全運維的需求。在對文件上傳介面進行安全審計的時候發現,其對上傳文件名沒有過濾處理,然後直接寫入磁碟(部分代碼如下)

隱隱覺得可以搞搞任意文件寫入漏洞(jumpserver web 控制面板都是以root許可權運行,如果可以任意文件寫入,危害呵呵,你懂得)。啟動burpsuite ,攔截請求,修改文件名(含有目錄穿越字元),但是結果沒成功,調試發現upload_file.name 已經是../../等目錄穿越字元過濾後的結果,有點奇怪,莫非是框架自動幫我過濾掉了,好奇心驅使我必須弄明白其中的原理,於是有了本文。

0x01. 分析過程

切入點就是request.FILES 對象的由來,整個流程涉及到5個模塊,如下:

django.core.handlers.wsgi

django.http.request

django.http.multipartparser

django.core.files.uploadhandler

django.core.files.uploadedfile

request.FILES 是一個類似於dict的對象,上傳文件輸入框name屬性的值為鍵名,鍵值指向處理後的文件對象(框架會調用指定的文件處理器處理),這個文件對象就是django.core.files.uploadedfile 模塊中UploadedFile類的實例。詳細分析如下:

訪問request.FILES 就是訪問 django.core.handlers.wsgi 模塊中WSGIRequest(繼承至django.http.request模塊的HttpRequest類)類的FILES屬性。

也即訪問WSGIRequest._get_files,這個方法會先判斷是否已經解析過上傳的文件(也即判斷是否有_files屬性,其實FILES 就是_files,MultiValueDict 類的實例),跟進_load_post_and_files 方法(這是繼承至其父類django.http.request模塊的HttpRequest類中的方法),如下:

跟進parse_file_upload方法(這裡的data為WSGIRequest 的實例),如下:

這裡要先說下upload_handlers 成員,如下:

初始化upload_handlers的時候會調用django.core.files.uploadhandler模塊的load_handler載入系統默認的文件處理器,如下:

settings.FILE_UPLOAD_HANDLERS

默認就是指的紅框中的兩個文件處理器,大於2.5M的就用TemporaryFileUploadHandler 處理器,否則用MemoryFileUploadHandler。

初始化文件上傳處理器之後,就開始調用django.http.multipartparser 模塊的MultiPartParser 類的parse 方法對上傳文件進行解析處理,在解析處理過程中,會調用 handle_file_complete 對上傳後的文件進行再次處理(處理完成後就返回一個django.core.files.uploadedfile.UploadedFile類的實例, 這個實例對象會被添加到_files 對象中,然後由parse 方法返回此對象, 這個過程就包含文件名被過濾掉的過程),如下:

圖中的old_filed_name 即為上一個解析完畢的文件,跟進handle_file_complete ,如下:

跟進文件處理器的file_complete方法, 這個方法返回的就是處理後的文件對象,也就是0x00 圖中upload_file 變數指向的文件對象,這裡我們以MemoryFileUploadHandler 文件處理器為例進行說明:

也就是說0x00 中的upload_file 也即InMemoryUploadedFile 類的實例,所以調用upload_file.name 即調用InMemoryUploadedFile 的name屬性,如下:

調用InMemoryUploadedFile 的name屬性,即調用getname方法,在InMemoryUploadedFile  實例話的過程中有name的賦值操作(在其父類__init方法中)如下:

賦值操作就會觸發_set_name方法的執行:

在_set_name中就會對上傳的文件進行過濾處理,os.path.basename(name)防止了目錄穿越漏洞,所以我們在0x00 圖中使用uploadfile.name獲取到的是經過os.path.basename 處理後的文件名,當然沒法任意文件寫入了

0x02. 總結

梳理完成之後,終於對Django 文件上傳中的安全機制有了一些了解,解決了我的困惑,像Django 這種現代的web框架對傳統的安全漏洞(比如XSS,CSRF、文件上傳等)都做了比較好的處理,在開發中,這些都是值得我們借鑒和學習的地方。

*本文原創作者:ForrestX386,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載


推薦閱讀:

淺談網路安全風險評估

TAG:安全 | 測試 | 框架 | Django框架 | 文件上傳 | 文件 | 安全測試 | 機制 | 分析 |