檔案上傳是 Web 應用高風險功能,必須嚴格驗證 MIME 類型、副檔名、檔案大小,並以隨機檔名儲存於非公開目錄,防止惡意腳本執行。
<?php
$allowedTypes = ['image/gif', 'image/jpeg', 'image/png', 'image/webp'];
$allowedExts = ['gif', 'jpg', 'jpeg', 'png', 'webp'];
$maxSize = 2 * 1024 * 1024; // 2MB
$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
$file = $_FILES['file'];
// 1. 錯誤碼檢查
if ($file['error'] !== UPLOAD_ERR_OK) {
$errors = [
UPLOAD_ERR_INI_SIZE => '超過 php.ini upload_max_filesize',
UPLOAD_ERR_FORM_SIZE => '超過表單 MAX_FILE_SIZE',
UPLOAD_ERR_PARTIAL => '上傳不完整',
UPLOAD_ERR_NO_FILE => '未選擇檔案',
];
die($errors[$file['error']] ?? '上傳錯誤');
}
// 2. 檔案大小
if ($file['size'] > $maxSize) {
die('檔案過大,限制 2MB');
}
// 3. MIME 類型(參考值,不可全信)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
if (!in_array($mime, $allowedTypes, true)) {
die('不允許的檔案類型');
}
// 4. 副檔名驗證
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowedExts, true)) {
die('不允許的副檔名');
}
// 5. 進階:檢查圖片真實性(防偽裝)
$imgInfo = getimagesize($file['tmp_name']);
if ($imgInfo === false) {
die('非有效圖片檔案');
}
// 可選:二次轉存確保無殘留腳本
// $dst = imagecreatefromstring(file_get_contents($file['tmp_name']));
// imagejpeg($dst, $newPath); imagedestroy($dst);
// 6. 產生安全檔名
$safeName = bin2hex(random_bytes(16)) . '.' . $ext;
$destPath = $uploadDir . $safeName;
// 7. 移動檔案
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
die('檔案儲存失敗');
}
// 8. 設定檔案權限(不可執行)
chmod($destPath, 0644);
echo json_encode(['success' => true, 'path' => $safeName, 'url' => '/uploads/' . $safeName]);
}
?>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="2097152">
<input type="file" name="file" accept="image/*">
<button type="submit">上傳</button>
</form>
| 風險 | 防護措施 |
|---|---|
| 惡意腳本上傳 | MIME + 副檔名雙重驗證、getimagesize、二次轉存、隨機檔名、儲存目錄禁用執行權限 |
| 檔名路徑遍歷 | 不使用原始檔名、basename()、路徑正規化 |
| 大檔案耗盡磁碟 | upload_max_filesize、post_max_size、程式碼層面 $file['size'] 檢查 |
| DoS 攻擊 | 限流、驗證碼、非同步處理佇列 |