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

PHP 文件上傳完整實戰:類型白名單、大小限制、隨機重命名、安全防禦與錯誤處理

     編輯:PHP 綜合知識

前言

文件上傳是 Web 應用高風險功能點。本文提供一個生產級上傳處理流程:MIME 類型白名單 + 擴展名二次驗證 + 大小限制 + 隨機文件名防覆蓋 + 目錄權限隔離 + 完整錯誤碼處理,並解析常見繞過手法與防禦措施。

核心代碼:安全上傳處理器

<?php
/**
 * 安全文件上傳處理
 *
 * @param array $file $_FILES['field'] 子數組
 * @param array $config 配置項
 * @return array ['success'=>bool, 'message'=>string, 'path'=>string|null]
 */
function secureUpload(array $file, array $config = []): array
{
    // 默認配置
    $cfg = array_merge([
        'allowedMimes' => [
            'image/gif',
            'image/jpeg',
            'image/png',
            'image/webp',
        ],
        'allowedExts' => ['gif', 'jpg', 'jpeg', 'png', 'webp'],
        'maxSize' => 2 * 1024 * 1024, // 2MB
        'uploadDir' => __DIR__ . '/uploads',
        'randomName' => true,
    ], $config);

    // 1. 檢查上傳錯誤碼
    if ($file['error'] !== UPLOAD_ERR_OK) {
        $msg = match ($file['error']) {
            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 => '未知上傳錯誤',
        };
        return ['success' => false, 'message' => $msg, 'path' => null];
    }

    // 2. 文件大小二次檢查(防範客戶端繞過)
    if ($file['size'] > $cfg['maxSize']) {
        return ['success' => false, 'message' => '文件體積超過限制(' . ($cfg['maxSize']/1024/1024) . 'MB)', 'path' => null];
    }

    // 3. 擴展名檢查(從原文件名提取,轉小寫)
    $origName = $file['name'];
    $ext = strtolower(pathinfo($origName, PATHINFO_EXTENSION));
    if (!in_array($ext, $cfg['allowedExts'], true)) {
        return ['success' => false, 'message' => "不允許的文件類型:.$ext", 'path' => null];
    }

    // 4. MIME 類型檢查(使用 finfo,比 $_FILES['type'] 可靠)
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = $finfo->file($file['tmp_name']);
    if (!in_array($mime, $cfg['allowedMimes'], true)) {
        return ['success' => false, 'message' => "檢測到非法文件類型:$mime", 'path' => null];
    }

    // 5. 可選:圖片二次驗證(getimagesize 確認真實圖片)
    if (str_starts_with($mime, 'image/')) {
        $imgInfo = @getimagesize($file['tmp_name']);
        if ($imgInfo === false) {
            return ['success' => false, 'message' => '文件不是有效的圖片', 'path' => null];
        }
        // 可選:限制圖片尺寸
        // if ($imgInfo[0] > 2000 || $imgInfo[1] > 2000) { ... }
    }

    // 6. 確保上傳目錄存在且不可執行
    if (!is_dir($cfg['uploadDir'])) {
        @mkdir($cfg['uploadDir'], 0755, true);
    }
    // 防止腳本執行:.htaccess / nginx 配置禁止 php 執行(此處略)

    // 7. 生成安全文件名
    if ($cfg['randomName']) {
        $safeName = bin2hex(random_bytes(16)) . '.' . $ext; // 32位隨機十六進制
    } else {
        // 保留原名但過濾危險字符
        $base = preg_replace('/[^a-zA-Z0-9._-]/', '_', pathinfo($origName, PATHINFO_FILENAME));
        $safeName = $base . '.' . $ext;
    }
    $destPath = $cfg['uploadDir'] . DIRECTORY_SEPARATOR . $safeName;

    // 8. 移動文件(move_uploaded_file 會檢查是否為真實上傳文件)
    if (!move_uploaded_file($file['tmp_name'], $destPath)) {
        return ['success' => false, 'message' => '文件保存失敗(權限/磁盤空間)', 'path' => null];
    }

    // 9. 設置文件權限(僅讀/寫,不可執行)
    @chmod($destPath, 0644);

    return ['success' => true, 'message' => '上傳成功', 'path' => $destPath];
}

// ========== 使用示例 ==========
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $result = secureUpload($_FILES['avatar'] ?? ['error' => UPLOAD_ERR_NO_FILE]);
    header('Content-Type: application/json');
    echo json_encode($result, JSON_UNESCAPED_UNICODE);
    exit;
}
?>
<form method="post" enctype="multipart/form-data">
    <input type="file" name="avatar" accept="image/*" required>
    <button type="submit">上傳頭像</button>
</form>

關鍵安全點深度解析

風險攻擊手法防禦措施
腳本上傳執行上傳 shell.php.jpg、.htaccess、雙擴展名、Apache 解析漏洞1. 隨機文件名 2. 目錄禁用腳本執行 3. MIME+擴展名雙驗證 4. 文件內容檢測
MIME 偽裝修改 Content-Type header、在圖片末尾追加 PHP 代碼使用 finfo 讀取文件魔數、getimagesize 驗證圖片結構
大小繞過客戶端修改 HTML max-size、攔截請求修改服務端強制檢查 $file['size']、PHP upload_max_filesize/post_max_size
路徑遍歷文件名包含 ../../etc/passwd隨機文件名、basename()、嚴格過濾路徑分隔符
覆蓋關鍵文件上傳同名文件覆蓋配置/腳本隨機文件名、目錄隔離、權限最小化
DoS(磁盤填滿)大量並發上傳大文件限制單文件/總大小、頻率限制、異步處理隊列、磁盤配額

Nginx/Apache 禁用上傳目錄腳本執行配置

Nginx

location /uploads/ {
    location ~ \.php$ { return 403; }
}

Apache (.htaccess 放在 uploads 目錄)

SetHandler default-handler
php_flag engine off
Options -ExecCGI
AddHandler cgi-script .php .php3 .php4 .php5 .phtml .pl .py .jsp .asp .sh .cgi

    Require all denied

進階擴展建議

  • 異步處理:大文件/視頻上傳採用分片上傳 + 消息隊列(RabbitMQ/Redis Stream)異步轉碼、掃毒、存儲。
  • 病毒掃描:集成 ClamAV(clamscan)或雲端 API(VirusTotal)在移動前掃描臨時文件。
  • 雲存儲直傳:簽名直傳至 OSS/S3,後端僅驗證回調,減輕服務器帶寬壓力。
  • 圖片處理管線:上傳後自動生成縮略圖、WebP 轉換、水印、元數據剝離(imagick/gd)。

總結

安全文件上傳的核心原則是「不信任任何客戶端數據」:隨機化文件名、雙重類型驗證、目錄執行權限隔離、完整錯誤碼處理。本文代碼已在多個項目中驗證,可直接作為基礎組件接入項目,並根據業務擴展異步、掃毒、雲存儲等能力。

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