上傳檔案
如果你希望使用者將檔案上傳到你的伺服器,你需要在將上傳的檔案實際移動到你的 Web 目錄之前進行一些安全檢查。
上傳的資料:
此陣列包含使用者提交的資料,而不是有關檔案本身的資訊。雖然通常這些資料是由瀏覽器生成的,但是可以使用軟體輕鬆地對同一表單釋出請求。
$_FILES['file']['name'];
$_FILES['file']['type'];
$_FILES['file']['size'];
$_FILES['file']['tmp_name'];
name
- 驗證它的每個方面。type
- 切勿使用此資料。它可以通過使用 PHP 函式來獲取。size
- 使用安全。tmp_name
- 使用安全。
利用檔名
通常,作業系統不允許檔名中的特定字元,但通過欺騙請求,你可以新增它們以允許意外事件發生。例如,讓我們命名檔案:
../script.php%00.png
好好看看那個檔名,你應該注意到一些事情。
- 首先要注意的是
../
,在檔名中是完全非法的,同時如果你將檔案從 1 個目錄移動到另一個目錄則完全沒問題,我們會做得對嗎? - 現在你可能會認為你在指令碼中正確驗證了副檔名,但是這個漏洞依賴於 url 解碼,將
%00
轉換為null
字元,基本上對作業系統說,這個字串在這裡結束,從檔名中刪除.png
。
所以現在我已經將 script.php
上傳到另一個目錄,通過簡單的驗證到副檔名。它還繞過 .htaccess
檔案,禁止在你的上傳目錄中執行指令碼。
安全地獲取檔名和副檔名
你可以使用 pathinfo()
以安全的方式推斷名稱和副檔名,但首先我們需要替換檔名中不需要的字元:
// This array contains a list of characters not allowed in a filename
$illegal = array_merge(array_map('chr', range(0,31)), ["<", ">", ":", '"', "/", "\\", "|", "?", "*", " "]);
$filename = str_replace($illegal, "-", $_FILES['file']['name']);
$pathinfo = pathinfo($filename);
$extension = $pathinfo['extension'] ? $pathinfo['extension']:'';
$filename = $pathinfo['filename'] ? $pathinfo['filename']:'';
if(!empty($extension) && !empty($filename)){
echo $filename, $extension;
} else {
die('file is missing an extension or name');
}
雖然現在我們有一個可用於儲存的檔名和副檔名,但我仍然更喜歡將該資訊儲存在資料庫中,併為該檔案提供生成的名稱,例如,md5(
uniqid().microtime())
+----+--------+-----------+------------+------+----------------------------------+---------------------+
| `id` | title | extension | mime | size | filename | time |
+----+--------+-----------+------------+------+----------------------------------+---------------------+
| `1` | myfile | txt | text/plain | 1020 | 5bcdaeddbfbd2810fa1b6f3118804d66 | 2017-03-11 00:38:54 |
+----+--------+-----------+------------+------+----------------------------------+---------------------+
這將解決檔名中重複檔名和未預見漏洞的問題。它還會導致攻擊者猜測該檔案的儲存位置,因為他或她無法專門針對該檔案執行。
Mime 型驗證
檢查副檔名以確定它是什麼檔案是不夠的,因為檔案可能命名為 image.png
但可能包含一個 PHP 指令碼。通過針對副檔名檢查上載檔案的 mime 型別,你可以驗證檔案是否包含其名稱所指的內容。
你甚至可以更進一步驗證影象,這實際上是開啟它們:
if($mime == 'image/jpeg' && $extension == 'jpeg' || $extension == 'jpg'){
if($img = imagecreatefromjpeg($filename)){
imagedestroy($img);
} else {
die('image failed to open, could be corrupt or the file contains something else.');
}
}
白名單列出你的上傳
最重要的是,你應該根據每個表單將副檔名和 mime 型別列入白名單。
function isFiletypeAllowed($extension, $mime, array $allowed)
{
return isset($allowed[$mime]) &&
is_array($allowed[$mime]) &&
in_array($extension, $allowed[$mime]);
}
$allowedFiletypes = [
'image/png' => [ 'png' ],
'image/gif' => [ 'gif' ],
'image/jpeg' => [ 'jpg', 'jpeg' ],
];
var_dump(isFiletypeAllowed('jpg', 'image/jpeg', $allowedFiletypes));