Skip to content

文件上传安全处理

文件上传功能如果处理不当,可能导致严重的安全漏洞。本文介绍安全的文件上传实现方法。

基础安全上传代码

php
function uploadFile($file, $uploadDir = 'uploads/') {
    // 1. 检查上传错误
    if ($file['error'] !== UPLOAD_ERR_OK) {
        throw new Exception('上传失败,错误码:' . $file['error']);
    }

    // 2. 验证文件大小(限制 2MB)
    $maxSize = 2 * 1024 * 1024;
    if ($file['size'] > $maxSize) {
        throw new Exception('文件过大,最大允许 2MB');
    }

    // 3. 使用 finfo 检测真实 MIME 类型
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mimeType = $finfo->file($file['tmp_name']);

    $allowedTypes = [
        'image/jpeg' => 'jpg',
        'image/png'  => 'png',
        'image/gif'  => 'gif',
        'application/pdf' => 'pdf'
    ];

    if (!isset($allowedTypes[$mimeType])) {
        throw new Exception('不支持的文件类型');
    }

    // 4. 生成安全的文件名(不使用原始文件名)
    $extension = $allowedTypes[$mimeType];
    $filename = bin2hex(random_bytes(16)) . '.' . $extension;
    $filepath = $uploadDir . $filename;

    // 5. 确保目录存在且可写
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }

    // 6. 移动文件
    if (!move_uploaded_file($file['tmp_name'], $filepath)) {
        throw new Exception('文件保存失败');
    }

    return $filepath;
}

// 使用示例
try {
    $path = uploadFile($_FILES['avatar']);
    echo "上传成功:$path";
} catch (Exception $e) {
    echo "错误:" . $e->getMessage();
}

图片额外处理(防止恶意图片)

php
function processImage($sourcePath, $destPath, $maxWidth = 1920, $maxHeight = 1080) {
    // 使用 getimagesize 验证图片完整性
    $imageInfo = getimagesize($sourcePath);
    if ($imageInfo === false) {
        throw new Exception('无效的图片文件');
    }

    // 重新生成图片(清除可能的恶意代码)
    list($width, $height, $type) = $imageInfo;

    switch ($type) {
        case IMAGETYPE_JPEG:
            $src = imagecreatefromjpeg($sourcePath);
            break;
        case IMAGETYPE_PNG:
            $src = imagecreatefrompng($sourcePath);
            break;
        default:
            throw new Exception('不支持的图片格式');
    }

    // 缩放处理
    $ratio = min($maxWidth / $width, $maxHeight / $height, 1);
    $newWidth = (int)($width * $ratio);
    $newHeight = (int)($height * $ratio);

    $dst = imagecreatetruecolor($newWidth, $newHeight);
    imagecopyresampled($dst, $src, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);

    // 保存新图片
    imagejpeg($dst, $destPath, 90);

    imagedestroy($src);
    imagedestroy($dst);

    // 删除原始上传文件
    unlink($sourcePath);
}

安全配置检查

php
// php.ini 推荐配置
file_uploads = On
upload_max_filesize = 2M
post_max_size = 8M
upload_tmp_dir = /var/www/uploads/tmp

// .htaccess 保护上传目录
// 放在 uploads/.htaccess
<FilesMatch "\.(php|php5|phtml|pl|py|jsp|asp|sh|cgi)$">
    Order allow,deny
    Deny from all
</FilesMatch>

常见攻击防范

攻击类型防范方法
双扩展名攻击不使用原始文件名,强制指定扩展名
MIME 伪造使用 finfo 检测真实类型
文件包含漏洞上传目录不允许执行脚本
目录遍历验证文件名,移除路径分隔符
文件大小攻击限制文件大小并验证

注意事项

  1. 永远不要 信任 $_FILES['name'],使用生成的文件名
  2. 上传目录应该设置为不可执行脚本
  3. 考虑使用云存储(OSS/S3)代替本地存储
  4. 敏感文件建议存储在 Web 根目录之外

Binstork