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

PHP 檔案上傳驗證實戰:副檔名檢查、圖片尺寸限制與安全最佳實踐

     編輯:PHP 相關知識

核心驗證函數庫

1. 副檔名白名單驗證

/**
 * 檢查上傳檔案副檔名是否在允許清單中
 * @param string $filename 檔案名稱
 * @param array  $allowedExt 允許的副檔名陣列,如 ['jpg','png','gif']
 * @return bool|string 通過回傳 true,失敗回傳錯誤訊息
 */
function validateExtension(string $filename, array $allowedExt) {
    if (strlen(trim($filename)) < 5) return '未選擇檔案';
    
    $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    if (empty($ext)) return '無法識別副檔名';
    
    $allowed = array_map('strtolower', $allowedExt);
    if (!in_array($ext, $allowed)) {
        return '禁止的檔案類型:.' . $ext . ',允許類型:.' . implode(', .', $allowed);
    }
    return true;
}

2. 圖片尺寸驗證

/**
 * 檢查圖片寬高是否超過限制
 * @param string $tmpPath 暫存檔路徑 ($_FILES['file']['tmp_name'])
 * @param int    $maxWidth  最大寬度
 * @param int    $maxHeight 最大高度
 * @return bool|string
 */
function validateImageSize(string $tmpPath, int $maxWidth, int $maxHeight) {
    $info = @getimagesize($tmpPath);
    if (!$info) return '非有效圖片檔案或檔案損壞';
    
    [$width, $height, $type, $attr] = $info;
    if ($width > $maxWidth || $height > $maxHeight) {
        return "圖片尺寸 {$width}x{$height} 超過限制 {$maxWidth}x{$maxHeight}";
    }
    return true;
}

3. MIME 類型二次驗證(防偽裝)

/**
 * 結合 finfo 進行真實 MIME 偵測
 * @param string $tmpPath
 * @param array  $allowedMime 如 ['image/jpeg','image/png','image/gif']
 * @return bool|string
 */
function validateMimeType(string $tmpPath, array $allowedMime) {
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime  = $finfo->file($tmpPath);
    
    if (!in_array($mime, $allowedMime)) {
        return "檔案真實類型 {$mime} 不在允許清單中";
    }
    return true;
}

整合上傳處理流程

<?php
// config
$config = [
    'maxSize'      => 2 * 1024 * 1024, // 2MB
    'allowedExt'   => ['jpg', 'jpeg', 'png', 'gif', 'webp'],
    'allowedMime'  => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
    'maxWidth'     => 1920,
    'maxHeight'    => 1080,
    'uploadDir'    => __DIR__ . '/uploads/',
    'prefix'       => 'img_',
];

// 確保目錄存在
if (!is_dir($config['uploadDir'])) mkdir($config['uploadDir'], 0755, true);

$errors = [];
$file   = $_FILES['image'] ?? null;

if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
    $errors[] = '上傳失敗,錯誤代碼:' . ($file['error'] ?? '未知');
} else {
    // 1. 檔案大小
    if ($file['size'] > $config['maxSize']) {
        $errors[] = '檔案大小超過 ' . ($config['maxSize']/1024/1024) . 'MB 限制';
    }
    
    // 2. 副檔名
    $res = validateExtension($file['name'], $config['allowedExt']);
    if ($res !== true) $errors[] = $res;
    
    // 3. MIME 類型
    $res = validateMimeType($file['tmp_name'], $config['allowedMime']);
    if ($res !== true) $errors[] = $res;
    
    // 4. 圖片尺寸
    $res = validateImageSize($file['tmp_name'], $config['maxWidth'], $config['maxHeight']);
    if ($res !== true) $errors[] = $res;
    
    // 5. 通過所有驗證,移動檔案
    if (empty($errors)) {
        $ext     = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        $newName = $config['prefix'] . date('YmdHis') . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
        $dest    = $config['uploadDir'] . $newName;
        
        if (move_uploaded_file($file['tmp_name'], $dest)) {
            echo json_encode(['success' => true, 'filename' => $newName, 'path' => $dest]);
        } else {
            echo json_encode(['success' => false, 'message' => '檔案儲存失敗,請檢查目錄權限']);
        }
        exit;
    }
}

// 輸出錯誤
http_response_code(400);
echo json_encode(['success' => false, 'message' => implode(';', $errors)]);

前端配合(AJAX 上傳)

<form id="uploadForm" enctype="multipart/form-data">
    <input type="file" name="image" id="image" accept="image/*" required />
    <button type="submit">上傳</button>
    <div id="preview"></div>
</form>

<script>
document.getElementById('uploadForm').addEventListener('submit', async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    const res = await fetch('upload.php', { method: 'POST', body: formData });
    const data = await res.json();
    
    if (data.success) {
        document.getElementById('preview').innerHTML = 
            `<img src="/uploads/${data.filename}" style="max-width:300px" /><p>上傳成功</p>`;
    } else {
        alert('失敗:' + data.message);
    }
});
</script>

進階安全加固清單

風險防禦措施
雙重副檔名 (shell.php.jpg)只取最後一個點後字串;重新命名存檔
MIME 偽裝使用 finfo 偵測真實類型
圖片內嵌惡意代碼GD/Imagick 重新繪製壓縮(imagecreatefromstring -> imagejpeg)
目錄遍歷 (../../etc/passwd)basename() 取純檔名;路徑拼接用實際路徑
大檔案 DoSphp.ini upload_max_filesize、post_max_size、Nginx client_max_body_size
執行權限上傳目錄設定 php_flag engine off / Nginx location ~ \.php$ { deny all; }
檔案名碰撞時間戳 + 隨機字串 + hash 命名

常見錯誤代碼對照表

UPLOAD_ERR_OK           = 0  // 成功
UPLOAD_ERR_INI_SIZE     = 1  // 超過 php.ini upload_max_filesize
UPLOAD_ERR_FORM_SIZE    = 2  // 超過表單 MAX_FILE_SIZE
UPLOAD_ERR_PARTIAL      = 3  // 部分上傳
UPLOAD_ERR_NO_FILE      = 4  // 未選檔案
UPLOAD_ERR_NO_TMP_DIR   = 6  // 缺少暫存目錄
UPLOAD_ERR_CANT_WRITE   = 7  // 寫入失敗
UPLOAD_ERR_EXTENSION    = 8  // PHP 擴充套件中斷
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved