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

PHP 檔案上傳完整實作:從表單設計到安全儲存的最佳實踐

     編輯:PHP 綜合知識

檔案上傳是 Web 開發基礎功能,但也是安全漏洞高發區。本文系統性說明表單設計、後端處理、安全驗證、錯誤處理與進階優化,提供可直接用於生產環境的完整範例。

前端表單關鍵要素

<form action="upload.php" method="post" enctype="multipart/form-data">
    <!-- 必須:啟用檔案上傳編碼 -->
    <input type="hidden" name="MAX_FILE_SIZE" value="5242880"> <!-- 5MB,客戶端提示用 -->
    <input type="file" name="myfile" accept="image/*,.pdf" required>
    <button type="submit">上傳</button>
</form>
  • enctype="multipart/form-data":缺少會導致 $_FILES 為空
  • MAX_FILE_SIZE:瀏覽器端軟性限制,不可信任,後端必須再驗證
  • accept:提示瀏覽器過濾檔案類型,非安全邊界

後端核心處理流程

<?php
function handleUpload(array $file, array $options = []): array {
    // 預設配置
    $config = array_merge([
        'upload_dir' => __DIR__ . '/uploads/',
        'allowed_mimes' => ['image/jpeg','image/png','image/gif','image/webp','application/pdf'],
        'max_size' => 5 * 1024 * 1024, // 5MB
        'overwrite' => false,
    ], $options);

    // 1. 檢查上傳錯誤碼
    if ($file['error'] !== UPLOAD_ERR_OK) {
        return ['success' => false, 'error' => uploadErrorMessage($file['error'])];
    }

    // 2. 檢查檔案大小
    if ($file['size'] > $config['max_size']) {
        return ['success' => false, 'error' => '檔案大小超過限制'];
    }

    // 3. MIME 型別二次驗證 (防偽造)
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $realMime = finfo_file($finfo, $file['tmp_name']);
    finfo_close($finfo);

    if (!in_array($realMime, $config['allowed_mimes'], true)) {
        return ['success' => false, 'error' => "不允許的檔案類型: $realMime"];
    }

    // 4. 產生安全儲存檔名
    $ext = getExtensionFromMime($realMime);
    $filename = bin2hex(random_bytes(16)) . '.' . $ext;
    $destPath = $config['upload_dir'] . $filename;

    // 5. 確保目錄存在
    if (!is_dir($config['upload_dir'])) {
        mkdir($config['upload_dir'], 0755, true);
    }

    // 6. 覆蓋檢查
    if (file_exists($destPath) && !$config['overwrite']) {
        return ['success' => false, 'error' => '檔案已存在'];
    }

    // 7. 移動檔案 (關鍵:使用 move_uploaded_file 確保來源是合法上傳檔)
    if (!move_uploaded_file($file['tmp_name'], $destPath)) {
        return ['success' => false, 'error' => '檔案儲存失敗'];
    }

    // 8. 設定權限
    chmod($destPath, 0644);

    return [
        'success' => true,
        'filename' => $filename,
        'original_name' => $file['name'],
        'path' => $destPath,
        'size' => $file['size'],
        'mime' => $realMime,
        'url' => '/uploads/' . $filename // 根據實際 Web 路徑調整
    ];
}

function uploadErrorMessage(int $code): string {
    return match ($code) {
        UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => '檔案超過大小限制',
        UPLOAD_ERR_PARTIAL => '檔案上傳不完整',
        UPLOAD_ERR_NO_FILE => '未選擇檔案',
        UPLOAD_ERR_NO_TMP_DIR => '伺服器缺少暫存目錄',
        UPLOAD_ERR_CANT_WRITE => '伺服器寫入失敗',
        UPLOAD_ERR_EXTENSION => 'PHP 擴展阻止上傳',
        default => '未知上傳錯誤',
    };
}

function getExtensionFromMime(string $mime): string {
    return match ($mime) {
        'image/jpeg' => 'jpg', 'image/png' => 'png',
        'image/gif' => 'gif', 'image/webp' => 'webp',
        'application/pdf' => 'pdf', default => 'bin'
    };
}

使用範例

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $result = handleUpload($_FILES['myfile']);
    header('Content-Type: application/json');
    echo json_encode($result, JSON_UNESCAPED_UNICODE);
    exit;
}
?>
<!-- HTML 表單 -->

關鍵安全防護矩陣

攻擊向量風險防護措施
惡意腳本上傳 (.php, .phtml)遠端程式碼執行 (RCE)MIME 白名單 + finfo 驗證 + 副檔名映射 + 儲存目錄禁用 PHP 解析
檔名路徑遍歷 (../../etc/passwd)任意檔案寫入忽略原始檔名,使用隨機雜湊檔名
MIME 偽造 (Content-Type: image/jpeg 實為 PHP)繞過型別檢查伺服器端 finfo_file() 實際檢測檔案魔數
大檔案 DoS佔滿磁碟/記憶體多層大小限制:php.ini、MAX_FILE_SIZE、程式碼驗證
併發檔名衝突覆蓋他人檔案random_bytes(16) + 存在性檢查
暫存檔殘留資訊洩漏move_uploaded_file 會自動清理,異常時註冊 register_shutdown_function 清理

伺服器配置強化 (php.ini / Nginx / Apache)

; 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

; Nginx: 禁用上傳目錄腳本執行
location /uploads/ {
    location ~ \.php$ { return 403; }
    expires 30d;
    add_header X-Content-Type-Options nosniff;
}

; Apache .htaccess (uploads/)
 Require all denied 
Options -ExecCGI
AddHandler cgi-script .php .phtml .php3 .php4 .php5 .php7 .phps

進階功能擴展

  • 圖片處理:上傳後呼叫 Intervention/Image 生成縮圖、WebP、水印、EXIF 移除
  • 分塊上傳:大檔案使用 Uppy / Dropzone 前端分片,後端合併 (臨時目錄 + 合併腳本)
  • 雲端儲存:介面抽象 StorageAdapter,實作 LocalStorage, S3Storage, OSSStorage
  • 病毒掃描:clamscan 非同步掃描,通過才移至永久區
  • 資料庫記錄:建立 uploads 表關聯業務實體,支援軟刪除、版本控制

常見問題排查

  • $_FILES 為空 → 檢查 enctype、post_max_size、file_uploads=On
  • 上傳後檔案損壞 → 確認未對 tmp_name 做任何讀寫操作、move_uploaded_file 只呼叫一次
  • 權限錯誤 → 上傳目錄擁有者為 PHP-FPM 使用者 (www-data)、權限 0755
  • 中文檔名亂碼 → 使用隨機檔名避免編碼問題,原始檔名存資料庫

遵循「不信任客戶端、白名單驗證、隨機化儲存、權限最小化」原則,即可構建安全可靠的檔案上傳功能。

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