文件上傳漏洞防御——圖片寫馬的剔除
最近回顧了一下CasperKid大牛在2011年11月發布的Upload Attack Framework,非常有感觸,寫得非常好,想深入了解這個漏洞的都推薦看看。
上傳功能常見于圖片的上傳,例如博客頭像設置,廣告位圖片上傳等。
上傳檢測方法在paper中也寫的比較明朗,這里總結一下:
1. 客戶端使用JS對上傳圖片做檢測,例如文件大小,文件擴展名,文件類型
2. 服務端檢測,例如文件大小(免得拒絕服務),文件路徑(避免0x00截斷,目錄遍歷),文件擴展名(避免服務器以非圖片的文件格式解析文件),文件類型(避免修改Content-Type為image/jpeg等),文件內容(避免圖片寫馬)
上傳檢測繞過的方法,也總結一下:
1.客戶端檢測,相當于沒有檢測,可以使用HTTP代理例如burp繞過
2.服務端檢測,一般采用白名單+黑名單的方式,但也極有可能出紕漏。例如大小寫,不在名單內的特例,操作系統bt特性(windows系統會自動去掉文件名最后面的點和空格),0x00截斷,服務器文件解析漏洞,最后還有圖片寫馬繞過類型檢測
本篇博客重點講講圖片寫馬的檢測。
我們知道PHP中文件類型的檢測可以使用
1.$_FILES['uploaded']['type'];
2.getimagesize
兩種方式來判斷是否是正常圖片,其實只要在不破壞圖片文件格式的情況下,就可以繞過檢測
例如使用以下命令,將正常圖片與一句話php木馬綁定在一起生成一個新的文件的方式
copy /b tangwei.jpg+yijuhua.php tangweiyijuhua.jpg
我們查看新生圖片的內容,在圖片底端可以看到一句話木馬寫入,如下圖所示
strings tangweiyijuhua.jpg

接下來我們演示這張圖片是否能正常上傳。
試驗用到了兩個腳本
1.upload.html 上傳客戶端
Choose an image to upload
2. upload.php 上傳文件處理
這個腳本會檢測文件后綴與文件類型,符合白名單jpeg格式的才允許上傳,并打印出上傳文件的基本信息及顯示圖片。
注意:紅色字體部分可以先注釋掉,下一步演示中會使用到
if (isset($_POST['upload'])){
// 獲得上傳文件的基本信息,文件名,類型,大小,臨時文件路徑
$filename = $_FILES['uploaded']['name'];
$filetype = $_FILES['uploaded']['type'];
$filesize = $_FILES['uploaded']['size'];
print "
File name : $filename
";
print "
File type : $filetype
";
print "
File size : $filesize
";
$tmpname = $_FILES['uploaded']['tmp_name'];
print "
Temp File path : $tmpname
";
$uploaddir='/var/www/upload/';
$target_path=$uploaddir.basename($filename);
// 獲得上傳文件的擴展名
$fileext= substr(strrchr($filename,"."),1);
print "
File extension : $fileext
";
$serverip = $_SERVER['SERVER_ADDR'];
//判斷文件后綴與類型,合法才進行上傳操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
print $target_path." successfully uploaded !
";
//顯示上傳的圖片
print 'Origin image
';
//使用上傳的圖片生成新的圖片
$im = imagecreatefromjpeg($target_path);
//給新圖片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
print "
new file name $newfilename
";
$newimagepath = $uploaddir.$newfilename;
imagejpeg($im,$newimagepath);
//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)
print 'New image
';
}else{
print "
Your image was not uploaded.
";
}
}else{
print "
Your image was not uploaded.
";
}
}else{
print "
Your image was not uploaded.
";
}
?>
進行上傳操作,我們會發現寫馬后的圖片也能正常生成預覽,如下圖所示

帶有一句話木馬的圖片,如果配合文件解析攻擊(將圖片當成PHP或HTML等非圖片格式來解析),就能起到webshell的作用。
所以,我們需要剔除掉圖片中惡意代碼部分內容, paper中說可以采取二次渲染,剛開始有點被這個名詞嚇到,后來才明白是啥意思,其實就是根據用戶上傳的圖片,生成一個新的圖片,然后刪除用戶上傳的原始圖片,將新圖片存儲到數據庫中。這個過程,PHP開發會非常眼熟吧,這個不就是論壇頭像設置功能中根據用戶上傳圖片生成縮略圖需求的代碼實現大綱嘛。
接下來,我們試驗一下重新生成的圖片是否還包含惡意代碼
讓我們回到upload.php腳本的紅色字體部分,代碼功能是使用用戶上傳的圖片生成新的圖片,重新命名并在前端顯示
//使用上傳的圖片生成新的圖片
$im = imagecreatefromjpeg($target_path);
//給新圖片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
print "
new file name $newfilename
";
$newimagepath = $uploaddir.$newfilename;
imagejpeg($im,$newimagepath);
//顯示二次渲染后的圖片(使用用戶上傳圖片生成的新圖片)
print 'New image
';
關鍵函數imagecreatefromjpeg ,從jpeg生成新的圖片(類似的還有imagecreatefromgif,imagecreatefrompng等),下圖就是生成的新圖片。

查看新圖片的內容,會發現新生成的圖片文件沒有了綁定的一句話木馬,算是成功去掉了圖片寫馬了吧。
paper中說到了對于這種重新生成圖片的防御方法,也有兩種攻擊方式
1. 利用數據二義性,構造符合圖像數據格式的木馬代碼,在paper中只是個思路,不知道現在能不能正確構造出來這樣的圖像文件
2. 利用溢出,攻擊生成新圖片的函數,至于什么樣的惡意圖片會使得這個函數溢出,就不清楚了,希望大牛門指導
總結一下,對于文件上傳的防御,做好以下幾點,就投入與收益來看,應該足夠了吧
1. 客戶端初步檢測文件大小,文件擴展名,文件類型
2. 服務端檢測文件大小,文件擴展名(為了避免麻煩,可以替換上傳文件的文件名,就是將文件路徑與文件名都寫死),文件類型
3.服務端根據用戶圖片生成新的圖片存儲到數據庫