本文目录导读:

在 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']);
完整的上传扫描应该包括:
- 基础验证:文件大小、类型、扩展名
- 恶意代码扫描:检测PHP代码、危险函数安全扫描**:检测SQL注入、XSS、路径遍历
- 文件特定扫描:图片重生成、PDF检查
- 病毒扫描:集成ClamAV
- 日志记录:记录所有上传活动和可疑行为
建议在生产环境中使用多层安全防护,并定期更新扫描规则。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播