1. 文件类型伪造
风险
攻击者通过修改文件扩展名(如将
.php
改为.jpg
)绕过类型检查。双扩展名攻击(如
evil.php.jpg
),某些服务器可能解析为.php
文件。
处理方法
验证 MIME 类型:使用
finfo_file()
检测文件的真实类型:$fileInfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $fileInfo->file($_FILES['file']['tmp_name']);
$allowedMime = ['image/jpeg', 'image/png'];
if (!in_array($mimeType, $allowedMime)) {
die("文件类型不合法!");
}验证文件内容:通过
getimagesize()
确保文件是有效图片:if (!@getimagesize($_FILES['file']['tmp_name'])) {
die("文件不是有效图片!");
}限制扩展名(辅助措施):
$ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
$allowedExt = ['jpg', 'jpeg', 'png'];
if (!in_array($ext, $allowedExt)) {
die("不支持的文件扩展名!");
}
2. 恶意文件上传(Webshell)
风险
攻击者上传 PHP、ASP 等可执行脚本,通过 URL 直接访问执行。
处理方法
禁用上传目录脚本执行(服务器配置):
Apache:在
.htaccess
中设置:<Directory "/path/to/uploads">
php_flag engine off
Options -ExecCGI
RemoveHandler .php .phtml .php3
</Directory>Nginx:配置
location
块:location ~* ^/uploads/.*\.(php|phtml)$ {
deny all;
}重命名文件:避免保留原始文件名,使用随机名称:
$newName = uniqid('file_', true) . '.' . $ext; // 例:file_5f3d2a1b.jpg
3. 目录遍历攻击
风险
攻击者通过文件名包含
../
(如../../evil.php
)上传到非目标目录。
处理方法
使用
basename()
清理文件名:$fileName = basename($_FILES['file']['name']);
4. 文件覆盖攻击
风险
攻击者上传同名文件覆盖服务器原有文件。
处理方法
生成唯一文件名(参考上文“重命名文件”)。
5. 大文件攻击(DoS)
风险
上传超大文件耗尽服务器磁盘空间或带宽。
处理方法
限制文件大小:
$maxSize = 2 * 1024 * 1024; // 2MB
if ($_FILES['file']['size'] > $maxSize) {
die("文件大小超过限制!");
}配置 PHP 参数(
php.ini
):upload_max_filesize = 2M
post_max_size = 2M
6. 文件权限问题
风险
上传文件权限过高(如
777
),导致被篡改或执行。
处理方法
设置安全权限:
move_uploaded_file($tmpFile, $targetFile);
chmod($targetFile, 0644); // 仅允许所有者读写,其他用户只读
7. CSRF 攻击
风险
攻击者诱导用户提交恶意上传请求。
处理方法
添加 CSRF Token:
session_start();
// 生成 Token
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;
// 在表单中嵌入 Token
<input type="hidden" name="csrf_token" value="<?= $token ?>">
// 验证 Token
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die("非法请求!");
}
8. 恶意内容上传
风险
上传包含病毒、木马的文件。
处理方法
杀毒扫描:集成 ClamAV 等工具:
$filePath = $_FILES['file']['tmp_name'];
$output = shell_exec("clamscan $filePath");
if (strpos($output, 'Infected files: 0') === false) {
die("文件包含恶意内容!");
}
9. 错误信息泄露
风险
错误提示暴露服务器路径或敏感信息。
处理方法
自定义错误提示:
// 关闭 PHP 错误显示
ini_set('display_errors', 0);
// 统一返回模糊错误
try {
// 上传逻辑
} catch (Exception $e) {
die("上传失败,请重试!");
}
完整安全上传示例
<?php
session_start();
$csrfToken = $_SESSION['csrf_token'] ?? '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 验证 CSRF Token
if (empty($_POST['csrf_token']) || !hash_equals($csrfToken, $_POST['csrf_token'])) {
die("非法请求!");
}
$targetDir = "uploads/";
$tmpFile = $_FILES['file']['tmp_name'];
$originalName = basename($_FILES['file']['name']);
$uploadOk = true;
// 验证扩展名
$allowedExt = ['jpg', 'jpeg', 'png'];
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!in_array($ext, $allowedExt)) {
die("不支持的文件类型!");
}
// 验证 MIME 类型
$fileInfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $fileInfo->file($tmpFile);
$allowedMime = ['image/jpeg', 'image/png'];
if (!in_array($mimeType, $allowedMime)) {
die("文件类型不合法!");
}
// 验证是否为真实图片
if (!@getimagesize($tmpFile)) {
die("文件不是有效图片!");
}
// 限制文件大小(2MB)
$maxSize = 2 * 1024 * 1024;
if ($_FILES['file']['size'] > $maxSize) {
die("文件大小超过限制!");
}
// 生成唯一文件名
$newName = uniqid('img_', true) . '.' . $ext;
$targetFile = $targetDir . $newName;
// 移动文件并设置权限
if (move_uploaded_file($tmpFile, $targetFile)) {
chmod($targetFile, 0644);
echo "文件上传成功!";
} else {
echo "上传失败!";
}
}
?>
最佳实践总结
多层验证:扩展名 + MIME 类型 + 文件内容。
重命名文件:避免覆盖和路径遍历。
服务器配置
权限控制:限制文件权限和大小。
CSRF 防护:使用 Token 验证。
错误处理:避免泄露敏感信息。
定期审查:监控上传目录和日志。