無回顯的任意文件上傳
前言
在某次攻防中,遇到了cms站一個上傳點,上傳正常圖片會回顯路徑,上傳其他文件提示上傳失敗,且沒有過多信息回顯,一度以為沒有漏洞。
通過找源碼代碼審計后getshell。
測試
在某次測試過程中發現,通過泄漏的md5進入后臺,在后臺配置中有處“上傳logo”的功能。
通過一頓測試,上傳文件為圖片后綴但內容非圖片(文件頭),會提示“上傳的不是一張圖片”。
如果上傳圖片文件, 后綴改為php等 會提示“上傳失敗”。只有正常圖片+圖片后綴 會返回圖片路徑。
一時陷入僵局。


一頓百度找到了源代碼,那就從代碼審計入手吧。
進行代碼審計
首先找到上傳點的入口文件。

第一行 導入命名空間“phpWeChat” 內的 Upload 類,下面是引用包含一些文件。
再往下,$action是用于下面 switch函數內做索引匹配而調用不同功能的。
$action的值是通過 GET方式獲取 “action”參數的值。如果空,就用預設的imageupload
當url為 :get:url/index.php?action=imageupload 進入到“imageupload”代碼塊
switch($action) { case 'imageupload': $originalname=preg_replace('/[^a-z0-9_]/i','',$originalname); //過濾文件名特殊字符作用,i:不區分大小寫模式 $image=Upload::imageUpload($upload_file,$originalname); //圖片落地 if(is_image($image)) { 省略} //is_image() 獲取文件后綴
其中核心的代碼是$image 這一行。
通過使用命名空間中Upload類的imageUpload()方法獲取并處理圖片后落地,將結果返回。
返回的結果為在下一行if函數作為條件進行判斷 。而具體做了什么先看imageUpload()函數做了什么,返回什么結果。
先看Upload類代碼如下:

這里使用了命名空間,并在剛才的index.php文件內導入,來進行使用。
Upload類下,定義了幾個靜態方法,主要是圖片、視頻、壓縮包上傳,圖片放大等方法
主要看下被調用的imageUpload()方法

在if中的表達式,通過文件上傳變量 $_FILES 獲取圖片用此方法文件上傳后,會被存儲在服務器臨時目錄內,
上傳成功后將滿足if條件,進入if內代碼。前四行依次獲取上傳的圖片信息:文件名、臨時路徑、文件后綴、文件大小。
再經過兩個if判斷,判斷是否post傳入圖片、判斷文件是否超過限制大小。
到了209行這個關鍵點,會對文件進行檢查。

此處的if判斷內使用了getimagesize()函數,并檢查結果是否為數組,結果并取反
getimagesize()函數會文件頭進行檢查,來判斷文件是否為有效的圖片類型。并返回一個包含圖片信息的數組。如果上傳了非圖片內容,getimagesize()返回 false

所以上傳非圖片內容時,getimagesize()返回 false 并取反 此處會執行exit并彈框"上傳的不是一張圖片"
對該函數需要進行一個繞過。
此時就需要一個圖片馬進行繞過。或是利用gif文件的頭。
下面是一個正常的gif文件頭。

GIF89a圖形文件就是一個根據圖形交換格式(GIF)89a版(1989年7 月發行)進行格式化之后的圖形。在GIF89a之前還有87a版(1987年5月發行)
可構造內容,進行繞過。
GIF89a phpinfo();?>
條件滿足后,代碼往下執行。

前兩個變量分別賦值了,將要儲存到的 文件名和 目錄名
文件名取客戶端提交時間+隨機值,目錄名取當前年月日,然后make_dir創建文件夾。
到了218行,if內表達式 使用move_uploaded_file將文件從tmp臨時目錄移動到網站上傳目錄內,此時文件已經寫入硬盤目錄。move_uploaded_file執行成功后會返回 真值,進入if內進行圖片文件尺寸放大等操作。
成功后,這時將返回一個文件路徑結果

再回到index入口文件。$image結果是imageUpload()方法返回的文件路徑

$image在if判斷的條件內,被is_image()處理。

is_image()通過獲得后綴,然后在多個圖片后綴數組內進行匹配,匹配到返回真值,否則 假。
如果不為'gif','jpg','jpeg','png','bmp'內后綴,結果為假的,前臺將返回上傳失敗提示。

但其實在服務器端,文件已經落地,返回任何信息也只是掩耳盜鈴:

總結
1.文件寫入目錄前,文件的后綴直接使用用戶提交時的文件后綴,且未對后綴或文件類型進行檢查。
2.文件落地后才進行后綴檢查,這時的后綴檢查完全是馬后炮。
3.雖然對文件有有效性進行驗證,但getimagesize()函數是可通過文件頭(gif文件頭)繞過。
4.move_uploaded_file()函數在php5部分版本中 可以使用 “%00”截斷路徑名,繞過后綴。
5.文件名前綴,是取客戶端提交時間戳+四位隨機數,文件名可以被枚舉。
上述問題造成了任意文件上傳漏洞。