上傳檔案

如果你希望使用者將檔案上傳到你的伺服器,你需要在將上傳的檔案實際移動到你的 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. 首先要注意的是 ../,在檔名中是完全非法的,同時如果你將檔案從 1 個目錄移動到另一個目錄則完全沒問題,我們會做得對嗎?
  2. 現在你可能會認為你在指令碼中正確驗證了副檔名,但是這個漏洞依賴於 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 型別。

白名單列出你的上傳

最重要的是,你應該根據每個表單將副檔名和 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));