PHP 怎么PHP上传扫描

wen PHP项目 1

本文目录导读:

PHP 怎么PHP上传扫描

  1. 基础上传安全配置
  2. 恶意代码扫描
  3. 图像文件深度扫描
  4. 完整的上传扫描流程
  5. 上传处理入口
  6. 高级安全措施
  7. 错误处理和日志

在 PHP 中实现上传文件的安全扫描,需要结合多种方法,以下是完整的安全扫描方案:

基础上传安全配置

<?php
class SecureFileUpload {
    private $allowedTypes = [
        'image/jpeg' => 'jpg',
        'image/png' => 'png',
        'image/gif' => 'gif',
        'application/pdf' => 'pdf'
    ];
    private $maxFileSize = 5242880; // 5MB
    private $uploadDir = '/uploads/';
    public function upload($file) {
        // 1. 基础验证
        if (!$this->validateFile($file)) {
            return ['success' => false, 'error' => '文件验证失败'];
        }
        // 2. 安全检查
        $scanResult = $this->scanFile($file['tmp_name']);
        if (!$scanResult['safe']) {
            return ['success' => false, 'error' => $scanResult['message']];
        }
        // 3. 生成唯一文件名
        $filename = $this->generateUniqueName($file['name']);
        // 4. 移动文件
        $destination = $this->uploadDir . $filename;
        if (move_uploaded_file($file['tmp_name'], $destination)) {
            return ['success' => true, 'path' => $destination];
        }
        return ['success' => false, 'error' => '文件保存失败'];
    }
    private function validateFile($file) {
        // 检查错误
        if ($file['error'] !== UPLOAD_ERR_OK) {
            return false;
        }
        // 检查大小
        if ($file['size'] > $this->maxFileSize) {
            return false;
        }
        // 检查MIME类型
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mimeType = $finfo->file($file['tmp_name']);
        // 检查MIME类型白名单
        if (!array_key_exists($mimeType, $this->allowedTypes)) {
            return false;
        }
        // 验证文件扩展名
        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if ($this->allowedTypes[$mimeType] !== $extension) {
            return false;
        }
        return true;
    }
    private function generateUniqueName($originalName) {
        $extension = pathinfo($originalName, PATHINFO_EXTENSION);
        return date('YmdHis') . '_' . bin2hex(random_bytes(8)) . '.' . $extension;
    }
}

恶意代码扫描

<?php
class MalwareScanner {
    public function scan($filePath) {
        $content = file_get_contents($filePath);
        // 危险函数列表
        $dangerousFunctions = [
            'eval', 'exec', 'system', 'passthru', 'shell_exec', 
            'popen', 'proc_open', 'pcntl_exec'
        ];
        foreach ($dangerousFunctions as $func) {
            if (preg_match("/\b" . $func . "\s*\(/i", $content)) {
                return [
                    'safe' => false, 
                    'message' => "发现危险函数: {$func}"
                ];
            }
        }
        // 危险代码模式
        $dangerousPatterns = [
            '/<\?php/i',                    // PHP代码
            '/\b(base64_decode|gzinflate)\b/i',  // 编码函数
            '/\b(assert|create_function)\b/i',   // 执行函数
            '/\b($_POST|$_GET|$_REQUEST)\b/i',   // 超全局变量
            '/\b(file_put_contents|fwrite|fopen)\b/i', // 文件操作
        ];
        foreach ($dangerousPatterns as $pattern) {
            if (preg_match($pattern, $content)) {
                return [
                    'safe' => false, 
                    'message' => "发现危险代码模式"
                ];
            }
        }
        return ['safe' => true];
    }
    public function checkMagicNumbers($filePath) {
        $fp = fopen($filePath, 'rb');
        $content = fread($fp, 4096);
        fclose($fp);
        // 检查是否包含PHP代码
        if (preg_match('/<\?php/i', $content)) {
            return false;
        }
        // 检查是否包含Shebang
        if (strpos($content, '#!/usr/bin/') === 0) {
            return false;
        }
        return true;
    }
}

图像文件深度扫描

<?php
class ImageScanner {
    public function scanImage($filePath) {
        // 尝试获取图片信息
        $imageInfo = @getimagesize($filePath);
        if ($imageInfo === false) {
            return [
                'safe' => false,
                'message' => '不是有效的图片文件'
            ];
        }
        // 重新生成图片,去除潜在恶意代码
        return $this->recreateImage($filePath, $imageInfo);
    }
    private function recreateImage($filePath, $imageInfo) {
        $type = $imageInfo[2];
        $width = $imageInfo[0];
        $height = $imageInfo[1];
        // 根据类型创建新图片
        switch ($type) {
            case IMAGETYPE_JPEG:
                $image = imagecreatefromjpeg($filePath);
                // 重新生成
                $newImage = imagecreatetruecolor($width, $height);
                imagecopy($newImage, $image, 0, 0, 0, 0, $width, $height);
                imagejpeg($newImage, $filePath, 90);
                break;
            case IMAGETYPE_PNG:
                $image = imagecreatefrompng($filePath);
                $newImage = imagecreatetruecolor($width, $height);
                imagecopy($newImage, $image, 0, 0, 0, 0, $width, $height);
                imagepng($newImage, $filePath, 9);
                break;
            case IMAGETYPE_GIF:
                $image = imagecreatefromgif($filePath);
                $newImage = imagecreatetruecolor($width, $height);
                imagecopy($newImage, $image, 0, 0, 0, 0, $width, $height);
                imagegif($newImage, $filePath);
                break;
            default:
                return ['safe' => false, 'message' => '不支持的图片类型'];
        }
        imagedestroy($image);
        imagedestroy($newImage);
        return ['safe' => true, 'message' => '图片扫描完成'];
    }
    public function checkExifData($filePath) {
        // 读取并清除EXIF数据
        if (function_exists('exif_read_data')) {
            $exif = @exif_read_data($filePath);
            if ($exif) {
                // 可以在这里记录或清除EXIF数据
                // 实际上PHP没有直接的函数清除EXIF,需要通过修改图片实现
                return [
                    'safe' => true,
                    'has_exif' => true,
                    'data' => $exif
                ];
            }
        }
        return ['safe' => true, 'has_exif' => false];
    }
}

完整的上传扫描流程

<?php
class UploadScanner {
    private $securityScanners = [];
    public function __construct() {
        $this->securityScanners = [
            new MalwareScanner(),
            new ImageScanner()
        ];
    }
    public function processUpload($file) {
        try {
            // 1. 基础验证
            $fileValidator = new SecureFileUpload();
            // 2. 恶意代码扫描
            $malwareScanner = new MalwareScanner();
            $scanResult = $malwareScanner->scan($file['tmp_name']);
            if (!$scanResult['safe']) {
                throw new Exception($scanResult['message']);
            }
            // 3. 文件类型特定扫描
            $mimeType = mime_content_type($file['tmp_name']);
            if (strpos($mimeType, 'image/') === 0) {
                $imageScanner = new ImageScanner();
                $imageResult = $imageScanner->scanImage($file['tmp_name']);
                if (!$imageResult['safe']) {
                    throw new Exception($imageResult['message']);
                }
            }
            // 4. 文本文件内容检查
            if ($this->isTextFile($mimeType)) {
                $textResult = $this->scanTextContent($file['tmp_name']);
                if (!$textResult['safe']) {
                    throw new Exception($textResult['message']);
                }
            }
            // 5. 病毒扫描(可选,需要ClamAV)
            if (extension_loaded('clamav')) {
                $virusResult = $this->scanWithClamAV($file['tmp_name']);
                if (!$virusResult['safe']) {
                    throw new Exception('检测到病毒');
                }
            }
            return ['safe' => true, 'message' => '文件扫描通过'];
        } catch (Exception $e) {
            return ['safe' => false, 'message' => $e->getMessage()];
        }
    }
    private function isTextFile($mimeType) {
        return in_array($mimeType, [
            'text/plain', 'text/html', 'text/xml', 
            'application/json', 'application/xml'
        ]);
    }
    private function scanTextContent($filePath) {
        $content = file_get_contents($filePath);
        // 检查HTML注入
        if (preg_match('/<script[^>]*>/i', $content)) {
            return ['safe' => false, 'message' => '检测到HTML脚本注入'];
        }
        // 检查SQL注入
        if (preg_match('/\b(union|select)\b.*\b(from|where)\b/i', $content)) {
            return ['safe' => false, 'message' => '检测到SQL注入模式'];
        }
        // 检查路径遍历
        if (strpos($content, '..') !== false) {
            return ['safe' => false, 'message' => '检测到路径遍历'];
        }
        return ['safe' => true];
    }
    public function scanWithClamAV($filePath) {
        exec('clamscan ' . escapeshellarg($filePath) . ' 2>&1', $output, $returnCode);
        return [
            'safe' => $returnCode === 0,
            'message' => implode(' ', $output)
        ];
    }
}

上传处理入口

<?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!isset($_FILES['file'])) {
        die(json_encode(['success' => false, 'error' => '没有文件上传']));
    }
    $uploadScanner = new UploadScanner();
    // 开始扫描
    $scanResult = $uploadScanner->processUpload($_FILES['file']);
    if ($scanResult['safe']) {
        // 扫描通过,继续处理上传
        $uploader = new SecureFileUpload();
        $uploadResult = $uploader->upload($_FILES['file']);
        echo json_encode($uploadResult);
    } else {
        echo json_encode([
            'success' => false,
            'error' => $scanResult['message']
        ]);
    }
}

高级安全措施

<?php
// CSRF Token验证
// 文件上传前验证CSRF token
if (!isset($_POST['csrf_token']) || !validateCsrfToken($_POST['csrf_token'])) {
    die('CSRF token验证失败');
}
// 限速上传
// 使用简单的速率限制
$ip = $_SERVER['REMOTE_ADDR'];
$key = 'upload_rate_' . $ip;
$limit = 5; // 5次上传
$window = 3600; // 1小时
if ($redis->get($key) >= $limit) {
    die('上传过于频繁');
}
$redis->incr($key);
$redis->expire($key, $window);
// 双重验证:客户端和服务端
// 验证文件的哈希值
$fileHash = hash_file('sha256', $_FILES['file']['tmp_name']);
if (in_array($fileHash, $blockedHashes)) {
    die('该文件已被标记为恶意文件');
}

错误处理和日志

<?php
class UploadLogger {
    public static function log($message, $type = 'info') {
        $logFile = '/logs/upload_' . date('Y-m-d') . '.log';
        $message = date('[Y-m-d H:i:s]') . " [$type] " . $message . "\n";
        file_put_contents($logFile, $message, FILE_APPEND);
    }
    public static function logSuspiciousActivity($fileInfo, $reason) {
        self::log("可疑文件: {$fileInfo['name']} - IP: " . $_SERVER['REMOTE_ADDR'] . " - 原因: $reason", 'warning');
    }
}
// 在扫描失败时记录
$uploadLogger = new UploadLogger();
$uploadLogger->logSuspiciousActivity($_FILES['file'], $scanResult['message']);

完整的上传扫描应该包括:

  1. 基础验证:文件大小、类型、扩展名
  2. 恶意代码扫描:检测PHP代码、危险函数安全扫描**:检测SQL注入、XSS、路径遍历
  3. 文件特定扫描:图片重生成、PDF检查
  4. 病毒扫描:集成ClamAV
  5. 日志记录:记录所有上传活动和可疑行为

建议在生产环境中使用多层安全防护,并定期更新扫描规则。

抱歉,评论功能暂时关闭!