下面是一個簡單的檔案上傳表單
代碼如下 |
複製代碼 |
<form action="upload.php" method="post" enctype="multipart/form-data" name="form1"> <input type="file" name="file1" /><br /> <input type="submit" value="上傳檔案" /> <input type="hidden" name="MAX_FILE_SIZE" value="1024" /> </form> |
php的設定檔php.ini,其中選項upload_max_filesize指定允許上傳的檔案大小,預設是2M
$_FILES陣列變數
PHP使用變數$_FILES來上傳檔案,$_FILES是一個數組。如果上傳test.txt,那麼$_FILES數組的內容為:
代碼如下 |
複製代碼 |
$FILES Array { [file] => Array { [name] => test.txt //檔案名稱 [type] => text/plain //MIME類型 [tmp_name] => /tmp/php5D.tmp //臨時檔案 [error] => 0 //錯誤資訊 [size] => 536 //檔案大小,單位位元組 } } |
如果上傳檔案按鈕的name屬性值為file
代碼如下 |
複製代碼 |
<input type="file" name="file" /> |
那麼使用$_FILES['file']['name']來獲得用戶端上傳檔案名稱,不包含路徑。使用$_FILES['file']['tmp_name']來獲得服務端儲存上傳檔案的臨時檔案路徑
存放上傳檔案的檔案夾
PHP不會直接將上傳檔案放到網站根目錄中,而是儲存為一個臨時檔案,名稱就是$_FILES['file']['tmp_name']的值,開發人員必須把這個臨時檔案複製到存放的網站檔案夾中。
$_FILES['file']['tmp_name']的值是由PHP設定的,與檔案原始名稱不一樣,開發人員必須使用$_FILES['file']['name']來取得上傳檔案的原始名稱。
上傳檔案時的錯誤資訊
$_FILES['file']['error']變數用來儲存上傳檔案時的錯誤資訊,它的值如下:
錯誤資訊 數值 說 明
UPLOAD_ERR_OK 0 沒有錯誤
UPLOAD_ERR_INI_SIZE 1 上傳檔案的大小超過php.ini的設定
UPLOAD_ERR_FROM_SIZE 2 上傳檔案的大小超過HTML表單中MAX_FILE_SIZE的值
UPLOAD_ERR_PARTIAL 3 只上傳部分的檔案
UPLOAD_ERR_NO_FILE 4 沒有檔案上傳
檔案上傳漏洞
如果提供給網站訪問者上傳圖片的功能,那必須小心訪問者上傳的實際可能不是圖片,而是可以指定的PHP程式。如果存放圖片的目錄是一個開放的檔案夾,則入侵者就可以遠程執行上傳的PHP檔案來進行攻擊。
下面是一個簡單的檔案上傳例子:
代碼如下 |
複製代碼 |
<?php // 設定上傳檔案的目錄 $uploaddir = "D:/www/images/"; // 檢查file是否存在 if (isset($_FILES['file1'])) { // 要放在網站目錄中的完整路徑,包含檔案名稱 $uploadfile = $uploaddir . $_FILES['file1']['name']; // 將伺服器存放的路徑,移動到真實檔案名稱 move_uploaded_file($_FILES['file1']['tmp_name'], $uploadfile); } ?> …… <form method="post" enctype="multipart/form-data" name="form1"> <input type="file" name="file1" /><br /> <input type="submit" value="上傳檔案" /> <input type="hidden" name="MAX_FILE_SIZE" value="1024" /> </form> |
這個例子沒有檢驗檔案尾碼,可以上傳任意檔案,很明顯的上傳漏洞,要解決上面的問題很簡單,我們從一段代碼來看。
代碼如下 |
複製代碼 |
switch( $extension ) { case 'application/msword': $extension ='doc'; break; case 'application/vnd.ms-excel': $extension ='xls'; break; case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': $extension ='docx'; break; case 'application/vnd.ms-powerpoint': $extension ='ppt'; break; case 'application/pdf': $extension ='pdf'; break; case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': $extension ='xlsx'; break; default: die('只允許上傳doc,docx,xls,pdf,ppt檔案 <a href="wend.php">重新上傳</a>'); }
|
這個是用來限制使用者上傳的類型號或尾碼名了,這樣可以過濾簡單不能直接上傳php了,但是我們再看一個執行個體,你繪發現太可怕了。
用畫圖工具建立一個jpg或gif或png之類圖片格式,開頭一定是GIF或JPG或PNG之類。
將php網馬的頭部寫入GIF 如圖:
代碼如下 |
複製代碼 |
然後寫一個簡單的php上傳檔案處理(我趕時間隨便寫的,沒什麼美感):
<?php if($_FILES){ echo '以下是錯誤的$_FILES:<br/>'; echo "<pre>"; print_r($_FILES); echo "</pre>"; echo "以下是錯誤的getimagesize()<br/>"; echo "<pre>"; print_r(getimagesize($_FILES['bug']['tmp_name'])); echo "</pre>"; exit; $fp = fopen($_FILES['bug']['tmp_name'],"r"); $content = fread($fp,filesize ($_FILES['bug']['tmp_name'])); //echo $content 可以看到上傳的原始碼 } ?> <form action="" method="post" enctype="multipart/form-data"> <input type="file" name="bug" /> <input type="submit" > </form> |
就可以看到如圖這樣坑爹的效果了。
首先是print_r($_FILES) 直接顯示的是擴充的jpg結果。
然後是php函數getimagesize()的結果是gif(它以檔案開頭那段為判斷依據)。
在這種問題上php是無法解決的,但我們可以從伺服器目錄許可權來操作,下面提供一些解決辦。
其實,我們抓住幾個地方即可,我們先來分析下,既然使用者要上存檔案,而且檔案將是多種多樣格式;可能有的檔案內容與使用者傳入格式不一致,有的檔案內容還夾雜木馬代碼。 那麼,我們讓使用者上存檔案,跟網站檔案做一個分別授權,做隔離。
讓儲存上存目錄獨立開來,目錄許可權唯讀不能執行
這一步從系統設計加以授權,無論你上次什麼檔案,都不可能執行到。就算我不做任何檢測,你的檔案都上存到這裡了,也不會對我系統構成安全。(如果有使用者上存一些反動言語的圖片,那另外需要處理的)
不直接使用伺服器傳入值,所有都要進行檢測
這類跟我們做一切輸入都是有害原則一樣,對於用戶端傳入的:type, name ,都要進行判斷,不直接使用。對於要產生到某個目錄,某個檔案名稱。
檔案名稱最好方法是:自己寫死目錄(不要讀取傳入目錄),檔案名稱,最好自己隨機產生,不讀取使用者檔案名稱。副檔名,可以取最右邊"."後面字元。
以上2個方法,剛好從2個方面對上存做了整體約束。
方法2 : 儲存上存檔案名稱,按照自己指定目錄寫入,並且檔案名稱自己產生的。
方法1:只要保證檔案寫對了位置,然後從配置上,對寫入目錄進行許可權控制,這個是治本。可以做到,你無論上存什麼檔案,都讓你沒有許可權跳出去可以運行。
以上2個方法,一起使用,可以保證檔案正確存到地方,然後,許可權可以控制。 這裡順便說明下, 判斷使用者上存檔案是否滿足要求類型,就直接檢查副檔名,只要滿足副檔名就讓上存。 反正,做了執行許可權限制,你不按要求上存內容,也無妨。 反正,不能執行,也不會有多大危害性的。
正確步驟:
1.讀取檔案名稱,驗證副檔名是不是在範圍內
2.自己定義產生的檔案名稱,目錄,副檔名可以來自檔案名稱副檔名。 其它值,都自己配置,不讀取上存中內容
3.將檔案 移到新目錄(這個目錄使用權限設定唯讀)