可靠文件类型校验必须用 finfo_open() 读取 magic bytes,禁用 $_FILES'file' 和后缀判断;需联动 MIME 与扩展名校验,严格白名单匹配,并注意配置、权限及存储安全。

只靠 $_FILES['file']['type'] 或文件后缀判断上传类型,等于把门钥匙交给攻击者——它能绕过,而且非常容易。
finfo_open() 是唯一可信的 MIME 校验方式
浏览器传来的 $_FILES['file']['type'] 是纯客户端字段,改个请求头就能伪造;pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION) 也一样,shell.php.jpg 这种双后缀文件能骗过所有仅看扩展名的逻辑。真正可靠的方案是读取文件前几百字节的 magic bytes,而这正是 finfo_open() 干的事。
- 必须启用 PHP 的
fileinfo扩展(检查phpinfo()中是否有fileinfo support => enabled) - 调用时固定用
FILEINFO_MIME_TYPE模式,避免返回带; charset=...的完整字符串 - 传参必须是
$_FILES['file']['tmp_name'],不是$_FILES['file']['name']或其他路径 - 别用已废弃的
mime_content_type(),也别自己file_get_contents()+substr()匹配 magic number——规则太复杂,不值得重造轮子
最小可行代码:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);
if (!in_array($mimeType, ['image/jpeg', 'image/png', 'application/pdf'], true)) {
die('不支持的文件类型');
}
扩展名校验不能省,但必须和 MIME 联动
只做 MIME 校验还不够:某些服务器(比如 Apache 启用了多后缀解析)可能把 xxx.jpg.php 当成 PHP 执行;而有些 PDF 或 Office 文件在特定条件下会被识别为 application/x-empty,导致白名单误判。所以扩展名要查,但不是为了“信任”,而是为了“兜底”和“归一化”。
立即学习“PHP免费学习笔记(深入)”;
- 提取扩展名必须用
pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION),再strtolower()统一小写 - 不要用
explode('.', $name)+end(),遇到my.photo.jpg或含点号的文件名会出错 - 更稳妥的做法是:根据
$mimeType查映射表(如['image/jpeg' => 'jpg']),强制用推导出的扩展名生成新文件名,彻底丢弃原始后缀 - 如果 MIME 和扩展名不一致(比如
image/jpeg但后缀是.exe),直接拒绝——这大概率是伪装文件
常见配置与运行时陷阱
即使代码写对了,上传流程仍可能在半路失败,原因往往不在逻辑,而在环境或配置。
-
$_FILES['file']['tmp_name']为空?先检查$_FILES['file']['error'] === UPLOAD_ERR_OK,否则可能是超限、临时目录不可写或file_uploads = Off - PHP 配置项必须协同生效:
upload_max_filesize(单文件)、post_max_size(整个 POST,必须 ≥ 前者)、max_execution_time(大文件上传耗时长) - Linux 下注意上传目录权限,Web 进程用户(如
www-data)必须有写权限;Windows 下注意路径分隔符和反斜杠转义问题 - 别把上传文件存到 Web 可访问目录(如
./uploads/),应移至根目录外(如/var/www/uploads/),或至少禁用该目录下脚本执行(Nginx 的location ~ \.php$ { deny all; })
最常被忽略的一点:MIME 白名单必须严格区分大小写,且用 true 开启 in_array() 的全等比较——'IMAGE/JPEG' 和 'image/jpeg' 不是一回事,而某些老旧 finfo 数据库可能返回大写类型。



















