作者:天融信阿爾法實驗室
原文鏈接:https://mp.weixin.qq.com/s/tLyoN9JYRUAtOJTxWEP8DQ
0x01 前言
前段時間看到有篇文章是關于DedeCMS后臺文件上傳(CNVD-2022-33420),是繞過了對上傳文件內容的黑名單過濾,碰巧前段時間學習過關于文件上傳的知識,所以有了這篇文章,對DedeCMS的兩個文件上傳漏洞(CVE-2018-20129、CVE-2019-8362)做一個分析。
0x02 簡介
DedeCMS簡介:DedeCMS由上海卓卓網絡科技有限公司研發的國產PHP網站內容管理系統;具有高效率標簽緩存機制;允許對類同的標簽進行緩存,在生成 HTML的時候,有利于提高系統反應速度,降低系統消耗的資源。眾多的應用支持;為用戶提供了各類網站建設的一體化解決方案,在本版本中,增加了分類、書庫、黃頁、圈子、問答等模塊,補充一些用戶的特殊要求。
0x03 DedeCMS V5.7 SP2前臺文件上傳(CVE-2018-20129)
01 漏洞復現
復現環境:phpstudy、DedeCMS V5.7 SP2、php5.6.9 前提條件:會員模塊開啟、以管理員權限登錄。會員模塊默認情況下是不開啟的,需要管理員在后臺手動。
登錄到前臺以后找到內容中心,發表一篇文章,點擊下面編輯器中找到上傳圖片按鈕,其實這里原本想實現的功能就是一個簡單圖片上傳的功能。

然后使用BurpSuite抓包,把文件名稱從1.png改成1.png.p*hp,然后放包上傳。

在響應信息中得到上傳文件的保存地址,并且文件的后綴也是PHP。

但是當我們嘗試去訪問這個文件時會發現有的文件是不解析的,這跟我們上傳的文件有關系,這個問題我們后面再解釋。
上傳的腳本文件可以正常利用。

02 漏洞分析
從抓取的數據包可以看到提交路徑是/dedecmsgbk/include/dialog/select_images_post.php,跟進這個文件看一下進行了怎樣的處理。

在select_images_post.php文件中的第36行,對文件名稱進行了正則替換,正則會匹配回車符、換行符、制表符、*、 % 、/ 、?、<、>、 |、 “、 :、并至少匹配1次,把匹配到的內容替換成‘ ’(空),因為我們通過抓包把文件名稱改成了1.png.p*hp,所以經過替換會變成1.png.php。

緊接著在第38行對文件名稱再次驗證,文件名中只需要存在jpg、gif、png中任意一個,如果不存在程序就會提示錯誤信息,但這里有一個非常大的缺陷,就是程序只是驗證文件名稱中存在jpg、gif、png三個中的任意一個,并不是在驗證文件的后綴。所以我們上傳的文件名稱1.png.php是可以繞過這個限制的。既然這個限制這么輕松就可以繞過,那我們可不可以直接把文件名稱改成1.png.php,而不是1.png.p*hp呢?這個問題最后會進行解答。

程序在第44行對上傳文件的MIME類型進行驗證,這里進行白名單驗證,在$sparr數組中定義了六個允許上傳的MIME類型,然后把我們上傳文件的MIME去除兩端空格并轉變成小寫得到$imgfile_type,然后判斷$imgfile_type是否在數組$sparr,如果不存在程序就會提示錯誤信息。

漏洞的產生還有一個非常重要的原因,從第57行開始分析,用戶的UserID拼接上'-'再拼接上一段隨機字符形成$filename_name,$mdir是年(年份后兩位)月日,$mdir跟$filename_name拼接形成$filename=$mdir/$filename_name,然后使用explode函數按照'.'分割文件名$imgfile_name,形成數組$fs內容('1','png','php'),然后取出數組中的最后一個元素拼接到了$filename_name參數后面組成文件名,而數組的最后一個元素正好是PHP,所以PHP文件就可以上傳了。

并且在最后也可以看到完成的路徑。

程序的最后就是把上傳文件的信息保存到了數據庫中。
03 遺留問題
上面我們留下了兩個疑問:1.直接上傳PHP文件可不可行,2.為什么部分腳本文件上傳失效。接下來我們將解決這兩個問題。
1.直接上傳PHP文件可不可以

從返回信息中可以看到,不允許我們上傳這種類型的文件,在select_images_post.php文件中包含了config.php文件,config.php文件包含了common.inc.php文件,common.inc.php文件包含了uploadsafe.inc.php文件,在uploadsafe.inc.php文件的第33行對文件的后綴進行了驗證,定義了一些禁止上傳的文件后綴$cfg_not_allowall,所以直接上傳PHP文件是不可以的,上傳PHP文件要配合 select_images_post.php文件中替換為空的操作進行利用。

這里的提示信息跟上面我們看到的是一樣的。并且在這里面也發現了驗證MIME類型。

2.為什么部分腳本文件上傳不能利用
通過觀察我們上傳的PHP腳本文件,可以發現腳本文件被二次渲染了。比如這個:

但是對于PNG圖片把惡意代碼插入的IDAT數據塊的腳本文件可以避免被二次渲染,并且可以成功利用。

并成功執行命令。

04 漏洞修復
修復方式一:
在給文件名拼接后綴時,對后綴進行二次驗證。
比如說在select_images_post.php的第60行添加如下代碼。
如果再上傳1.png.p*hp文件,程序執行到$fs[count($fs)-1]會取出最后一個數組成員php,而$cfg_imgtype是jpg|gif|png,不包含,所以程序提示報錯信息,上傳失敗。

修復方式二:
在官方DedeCMS V5.7.93版本中,uploadsafe.inc.php文件中由原先只要文件名中包含$cfg_not_allowall參數定義的這些文件后綴,改成了使用pathinfo()方法獲取文件的后綴,然后判斷后綴是否存在黑名單中,按照之前的文件名來說的話,這里獲取的后綴是p*hp,依然不在黑名單數組中。

因為更新迭代,此時的富文本編輯器中的數據提交到了select_images_post_wangEditor.php文件中,這里的正則匹配特殊字符替換成空,但是這里的文件后綴也采用pathinfo()方法獲取文件后綴,之前文件名1.png.p*hp經過特殊字符替換成空,然后pathinfo()方法獲取獲取到的文件后綴為php,這里的白名單$cfg_imgtype是jpg|gif|png,顯然php并不在其中,所以返回提示信息"您所上傳的圖片類型不在許可列表"。

官方已修復該漏洞,請注意升級。補丁鏈接: https://www.dedecms.com/download
05 漏洞總結
從上面可以知道上傳p*hp后綴的原因是在正則替換之前存在著一個黑名單驗證,傳入p*hp后綴后可以繞過這個黑名單驗證然后被正則把*替換為空,而文件后綴獲取時會取出最后一個數組成員php,并沒有進行二次驗證,所以造成了這次文件上傳漏洞的產生。
0x04 DedeCMS V5.7 SP2后臺文件上傳(CVE-2019-8362)
01 漏洞復現
復現環境:phpstudy、DedeCMS V5.7 SP2、php5.6.9
首先準備一個壓縮包1.zip,壓縮包里面的文件名為1.jpg.php,文件內容為:

安裝完成之后登錄到系統后臺,默認賬號/密碼是admin/admin,點擊在左側當導航欄中的核心按鈕,然后選中附件管理中的文件式管理器,進入其中的soft目錄中。

然后把之前準備好的壓縮包上傳上去。

然后訪問album_add.php文件發布新圖集,這里需要提前創建一個圖集主欄目,上傳方式選擇從ZIP壓縮包中解壓圖片,選擇之前上傳的1.zip。

上傳完成之后,我們點擊預覽文檔。

然后就會跳轉到前臺頁面,點擊下面的testZip。

當點擊testZip,頁面跳轉,之前壓縮包內的1.jpg.php里面的代碼會執行。

我們來看一下這里正常上傳圖片的效果,當壓縮包內是圖片的話,這個會顯示出圖片,至于鏈接的話就是查看圖片的鏈接。


02 漏洞分析
使用burpSuite抓包,發現提交到了album_add.php文件,在/dede/album_add.php中找到這個文件,跟進程序看到底是怎么處理的。
album_add.php文件的前半部分都是一些驗證賦值操作,或者是驗證關于相關欄目的信息,但是因為上傳的時候選擇的是從壓縮包中解壓圖片,所以$formzip參數值為1,這個后面會用到。

因為$formzip參數值為1,所以會解壓壓縮包中的圖片,其實可以發現在程序的173行調用ExtractAll()方法完成解壓操作,傳入$zipfile和$tmpzipdir兩個參數,$zipfile是壓縮包的保存路徑,$tmpzipdir是創建出來存在解壓文件的路徑。

跟進到ExtractAll()方法,查看程序的下一步執行。

在程序第309行會調用get_List()方法獲取壓縮包中的信息。

這里要提一下ReadCentralFileHeaders()方法,在這個方法中會讀取到壓縮包中的文件名等信息。
然后回到ExtractAll()方法,接著調用Extract()方法解壓單個文件,這個方法中也會調用ReadCentralFileHeaders()方法讀取到壓縮包中的文件名等信息,然后在361行調用ExtractFile()方法,并把獲取壓縮包中文件信息($header)、壓縮包路徑($zip)、創建的目錄($to)這三個參數一起傳入。

ExtractFile()方法中的大致流程就是首先會創建的目錄下面創建一個gz文件,文件名稱是$header[‘filename’].gz拼接的,也就是1.jpg.php.gz,接下來在創建一個$header[‘filename’]文件,此時這個文件名就是1.jpg.php,再把讀取到的內容寫入進去,這個過程中并沒有對內容進行校驗。

從調試中可以看到,寫入的內容就是上傳壓縮包中文件的內容``,最后刪除gz文件。完成解壓。

完成解壓之后程序回到album_add.php文件中,在程序第176行,程序調用了GetMatchFiles()方法,并且傳入了三個參數,分別是$tmpzipdir、jpg|png|gif、$imgs。

回到album_add.php文件。此時的$imgs就是讀取到的文件名,此時會進行循環操作,循環中首先會組成一個保存路徑$savepath,是由$cfg_image_dir拼接上年月組成,$iurl是由$savepath進行一系列的拼接組成,最關鍵的點是在程序的第184行,取出$imgold參數的最后四個字符,$imgold就是GetMatchFiles()方法讀取到的文件路徑,其中最后四個字符就是.php,然后拼接在$iurl上面,$cfg_basedir跟$iurl組成文件名,所以此時的文件后綴就是php,然后利用copy()方法,把$imgold中的內容復制到$iurl中,并且刪除之前創建的ziptmp中的目錄,最后就是進行一些圖片的尺寸以及數據庫的操作。

程序繼續向下進行,在第209行,會把$iurl參數參入到數據庫中,就是把發表的信息都存入到數據庫中。

最后如果選擇刪除壓縮包,會把壓縮包刪除,再通過RmDirFiles()方法刪除對應創建的目錄$tmpzipdir。
當前臺調用的時候可以發現跳轉的鏈接,點擊超鏈接進入。

當要點擊標題鏈接時,在左下角也會發現同樣的URL,這個URL就是我們保存的PHP文件的路徑。

并且從代碼中我們也可以看見,這個鏈接是寫死在HTML文件中的。

所以點擊鏈接就會跳轉到PHP文件,執行文件中的代碼。
03 漏洞修復
我們把/dede/file_class.php中第161行代碼修改成:
else if (in_array(pathinfo($filename, PATHINFO_EXTENSION), explode("|", $fileexp),true) === TRUE)
這里之前是驗證文件名,現在改成驗證文件后綴,后續就會驗證文件后綴是否存在$fileexp中了。
最新版中使用pathinfo()方法獲取到文件的后綴,然后判斷是否后綴是否在白名單數組中。然后再重復一次之前的上傳流程,此時在這個斷點處可以看到,php后綴并不滿足判斷條件,所以此時的文件名并不就加入到$filearr中,$filearr數組就是當時作為參數傳入的$imgs數組,因為$imgs為空數組,所以接下來的循環復制操作也就不會執行了。

官方已修復該漏洞,請注意升級。補丁鏈接: https://www.dedecms.com/download
04 漏洞總結
在分析過程中可以看到,對于壓縮包中文件的后綴并沒有進行驗證,文件后綴的獲取也沒有進行二次驗證,而是直接獲取文件名稱的最后四個字符拼接上去,最終造成了文件上傳漏洞的產生。
0x05 總結
從上面兩個實例中可以看到,第一個漏洞有用到黑名單驗證,但并不是驗證的文件后綴,兩個漏洞都用白名單對文件名進行了驗證,保存文件時對于文件后綴的獲取并沒有進行二次驗證,而是直接獲取拼接,所以才會造成文件上傳漏洞的產生,造成系統的Getshell。對于上傳文件其實也要對內容進行驗證的,并且這兩個地方本來都是上傳圖片的功能,驗證上傳文件的頭部信息,對上傳文件進行二次渲染,在保存文件時對后綴進行二次驗證,這樣就可以極大程度的避免文件上傳漏洞的產生。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1910/
暫無評論