首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 檔案上傳完整實作:驗證、儲存、錯誤處理與安全防護

     編輯:PHP 相關知識

PHP 檔案上傳完整實作:驗證、儲存、錯誤處理與安全防護

檔案上傳是 Web 系統高風險功能,必須嚴格驗證類型、大小、檔名、儲存路徑,並防範惡意腳本執行。本文提供生產級上傳處理流程,包含前端表單、後端驗證邏輯、安全儲存策略、錯誤碼對照與進階防護清單。

前端表單 (HTML5)

<form action="upload.php" method="post" enctype="multipart/form-data">
    <input type="hidden" name="MAX_FILE_SIZE" value="2000000"> <!-- 客戶端提示,不可信 -->
    <label>姓名:<input type="text" name="name" required></label>
    <label>圖片:<input type="file" name="file" id="file" accept="image/jpeg,image/png,image/gif" required></label>
    <button type="submit">上傳</button>
</form>

後端處理核心邏輯 (upload.php)

<?php
declare(strict_types=1);

// 1. 設定常數
const UPLOAD_DIR      = __DIR__ . '/uploads/';
const ALLOWED_TYPES   = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif'];
const MAX_FILE_SIZE   = 2 * 1024 * 1024; // 2MB
const MAX_FILENAME_LEN = 255;

// 2. 確保上傳目錄存在且不可執行
if (!is_dir(UPLOAD_DIR)) {
    mkdir(UPLOAD_DIR, 0755, true);
    // 關鍵:建立 .htaccess / web.config 禁止腳本執行
    file_put_contents(UPLOAD_DIR . '.htaccess', "SetHandler default-handler\nOptions -ExecCGI\nphp_flag engine off");
}

$errors = [];
$savedPath = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 3. 基本錯誤碼檢查
    if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
        $errors[] = '上傳失敗:' . uploadErrorMessage($_FILES['file']['error'] ?? UPLOAD_ERR_NO_FILE);
    } else {
        $file = $_FILES['file'];

        // 4. 伺服器端二次驗證 (不可信任前端)
        // 4.1 大小
        if ($file['size'] > MAX_FILE_SIZE) {
            $errors[] = '檔案過大,限制 ' . (MAX_FILE_SIZE/1024/1024) . 'MB';
        }

        // 4.2 MIME 類型 (使用 finfo 而非 $_FILES['type'] 瀏覽器可偽造)
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mime  = $finfo->file($file['tmp_name']);
        if (!isset(ALLOWED_TYPES[$mime])) {
            $errors[] = '不支援的檔案類型:' . $mime;
        }

        // 4.3 檔名安全化
        $origName = basename($file['name']);
        if (strlen($origName) > MAX_FILENAME_LEN) {
            $errors[] = '檔名過長';
        }
        $ext = ALLOWED_TYPES[$mime];
        $safeName = bin2hex(random_bytes(16)) . '.' . $ext; // 隨機檔名,避免覆蓋/注入
        $destPath = UPLOAD_DIR . $safeName;

        // 5. 移動檔案 (atomic 操作,避免 TOCTOU)
        if (empty($errors)) {
            if (!move_uploaded_file($file['tmp_name'], $destPath)) {
                $errors[] = '伺服器儲存失敗,請稍後再試';
            } else {
                // 6. 權限收斂 (僅讀)
                chmod($destPath, 0644);
                $savedPath = $destPath;
            }
        }
    }
}

// 7. 回應 (AJAX 可回 JSON,傳統表單可重導向)
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
    'success' => empty($errors),
    'errors'  => $errors,
    'file'    => $savedPath ? ['path' => $savedPath, 'url' => '/uploads/' . basename($savedPath)] : null,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

/**
 * 上傳錯誤碼轉中文訊息
 */
function uploadErrorMessage(int $code): string {
    return match ($code) {
        UPLOAD_ERR_INI_SIZE   => '超過 php.ini upload_max_filesize',
        UPLOAD_ERR_FORM_SIZE  => '超過表單 MAX_FILE_SIZE',
        UPLOAD_ERR_PARTIAL    => '檔案僅部分上傳',
        UPLOAD_ERR_NO_FILE    => '未選擇檔案',
        UPLOAD_ERR_NO_TMP_DIR => '缺少暫存目錄',
        UPLOAD_ERR_CANT_WRITE => '寫入磁碟失敗',
        UPLOAD_ERR_EXTENSION  => '被 PHP 擴充套件中止',
        default               => '未知錯誤 (代碼: ' . $code . ')',
    };
}
?>

安全防護關鍵點清單

風險防護措施實作位置
惡意腳本上傳 (.php/.phtml/.php5)MIME 白名單 + 副檔名白名單 + 隨機重命名 + 目錄禁用執行權限驗證邏輯、.htaccess
檔名路徑遍歷 (../../etc/passwd)basename() + 隨機檔名 + realpath() 檢查路徑前綴檔名處理
大檔案耗盡磁碟/記憶體upload_max_filesize, post_max_size, MAX_FILE_SIZE 三層限制php.ini、表單、程式碼
MIME 偽造 (圖片夾帶 PHP 代碼)finfo 偵測真實類型 + getimagesize() 驗證圖片結構驗證邏輯
併發上傳檔名衝突隨機檔名 (random_bytes) 或 tempnam()檔名生成
上傳目錄可直接瀏覽/下載Nginx location /uploads { internal; } 或 PHP 代理下載Web Server 設定

進階擴充:圖片二次處理

// 上傳成功後,可同步/異步生成縮圖、水印、WebP 轉換
// 同步範例 (GD)
$img = imagecreatefromstring(file_get_contents($savedPath));
// ... 縮放/水印 ...
imagejpeg($img, $savedPath . '.thumb.jpg', 80);
imagedestroy($img);

// 生產環境建議:丟入佇列 (Redis/RabbitMQ) 由 Worker 異步處理,避免阻塞回應

配置檢查 (php.ini)

file_uploads = On
upload_max_filesize = 10M
post_max_size = 12M          ; 必須 > upload_max_filesize
max_file_uploads = 20        ; 單次請求最大檔案數
max_input_time = 60          ; 大檔案上傳需更長時間
memory_limit = 128M          ; 圖片處理需足夠記憶體

嚴格遵循「不信任任何客戶端資料、最小權限儲存、分層驗證」原則,才能構建安全可靠的檔案上傳功能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved