在使用 php 進行檔案的上傳和儲存時,很多人都會給檔案進行重名命並儲存到可寫檔案夾下,然後我們在其中一個失誤的地方便是採用上傳檔案的副檔名作為判斷檔案類型的依據。
這樣做其實與後門大開無異,舉一個簡單的例子,通過副檔名判斷一般是字串的截取判斷,或者是使用$_FILE數組判斷,然後如果使用者上傳的檔案名稱為 image.php.png, image.png.php, image.php%****.png 之類的話,判斷就會出錯。
另外,$_FILES['type']可能被篡改。
下面是我判斷檔案名稱的方法:
讀取檔案頭四個位元組作為判斷。
下面直接上代碼
我實現的是僅支援word和pdf檔案,且檔案大小小於512kb:
$tmpname = $_FILES ['userfile'] ['tmp_name'];if(is_uploaded_file($tmpname)) {$mimetype = detectMIME($tmpname);$tuozhanming = getFileExt($filename, $mimetype);if($tuozhanming == "type_error"){echo '僅支援word和pdf檔案,且檔案大小小於512kb:請重試';exit();}}else{$_FILES ['userfile'] ['error'] = 6;}if ($_FILES ['userfile'] ['error'] > 0) {echo 'Problem: ';switch ($_FILES ['userfile'] ['error']) {case 1 :echo '上傳檔案過大:請重試';break;case 2 :echo '上傳檔案過大:請重試';break;case 3 :echo '檔案上傳丟失:請重試';break;case 4 :echo '無檔案被上傳:請重試';break;case 6 :echo '僅支援word和pdf檔案,且檔案大小小於512kb:請重試';break;case 7 :echo '上傳檔案儲存體失敗:請重試';break;}exit ();}//判斷檔案類型//上傳檔案 $_FILES ['userfile'] ['name'] = time () . "." . $tuozhanming;$upfile = '../uploads/' . $_FILES ['userfile'] ['name'];if ( !move_uploaded_file ( $_FILES ['userfile'] ['tmp_name'], $upfile )) {echo 'Problem: 檔案移動失敗';exit ();}}function detectMIME($filename) {$file = fopen ( $filename, "rb" );$finfo = finfo_open ( FILEINFO_MIME );if (! $finfo) {// 直接讀取檔案的前4個位元組,根據寫入程式碼判斷$file = fopen ( $filename, "rb" );$bin = fread ( $file, 4 ); //唯讀檔案頭4位元組fclose ( $file );$strInfo = @unpack ( "C4chars", $bin );//dechex() 函數把十進位轉換為十六進位。$typeCode = dechex ( $strInfo ['chars1'] ) . dechex ( $strInfo ['chars2'] ) . dechex ( $strInfo ['chars3'] ) . dechex ( $strInfo ['chars4'] );$type = '';switch ($typeCode) //寫入程式碼值查表{case "504b34" :$type = 'application/zip; charset=binary';break;case "d0cf11e0" :$type = 'application/vnd.ms-office; charset=binary';break;case "25504446" :$type = 'application/pdf; charset=binary';break;default :$type = 'application/vnd.ms-office; charset=binary';break;}} else {//finfo_file return information of a file$type = finfo_file ( $finfo, $filename );}return $type;function getFileExt($filename, $type) {switch ($type) {case "application/zip; charset=binary" :$extType = "docx";break;case "application/vnd.ms-office; charset=binary" :$extType = "doc";break;case "application/msword; charset=binary" : $extType = "doc"; break;case "application/pdf; charset=binary" :$extType = "pdf";break;default :$extType = "type_error";break;}return $extType;}