不能只看$_FILES'file',因其是客户端自填字段,可被轻易伪造为image/jpeg而实际上传PHP木马;必须用finfo_file()读取临时文件二进制头部(magic bytes)进行真实MIME识别,并配合白名单、内容解析与执行隔离。

为什么不能只看 $_FILES['file']['type']
浏览器提交的 $_FILES['file']['type'] 是客户端自填字段,完全不可信。攻击者可轻易伪造为 image/jpeg,实际上传的是 PHP WebShell 文件。靠这个判断“是不是图片”,等于把门锁换成贴纸。
真实校验必须依赖服务端对文件**二进制内容**的分析。PHP 的 finfo_file() 函数正是为此设计——它读取文件头若干字节(magic bytes),比对内置 MIME 数据库,返回实际类型。
用 finfo_file 校验图片的正确姿势
关键不是调用函数,而是初始化方式和参数选择。错误初始化会导致始终返回 application/octet-stream 或抛出警告。
- 必须使用
FILEINFO_MIME_TYPE模式(不是FILEINFO_MIME,后者含字符集,不必要且易干扰判断) - 推荐显式指定 magic database 路径(尤其在 Windows 或容器环境),避免因系统未配置或路径不对导致 fallback 失效
- 传入参数必须是**临时文件的绝对路径**(
$_FILES['file']['tmp_name']),不能是 URL 或相对路径
示例:
$finfo = finfo_open(FILEINFO_MIME_TYPE, '/usr/share/misc/magic'); // Linux 常见路径
if (!$finfo) {
die('finfo 初始化失败');
}
$mimeType = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);
if (in_array($mimeType, ['image/jpeg', 'image/png', 'image/gif'])) {
// 通过校验
}
常见误判与绕过场景
finfo_file() 并非万能。以下情况仍可能漏放恶意文件:
- 图片末尾追加 PHP 代码(如 GIF89a 后写
<?php system($_GET[1]); ?>):finfo 只读头部,仍会返回image/gif - 利用图像格式解析器漏洞(如某些旧版 GD 库对 BMP 头部校验宽松):finfo 判定为
image/bmp,但后续处理时触发 RCE - 上传 WebP 文件但服务器未启用
imagewebp()支持:finfo 返回image/webp,但getimagesize()或imagecreatefromwebp()直接失败
所以生产环境必须组合校验:finfo_file() + getimagesize()(检查是否真能解析为图像结构)+ 白名单后缀重命名(如强制改为 .jpg)。
Windows 下 finfo_file 找不到 magic 文件怎么办
Windows 默认不带 magic database,finfo_open() 若不指定路径,常 fallback 到极简内置库,导致大量图片识别为 application/octet-stream。
解决方法只有两个可靠选项:
- 下载官方 magic 文件(如从 file project repo 获取
magic文件),放入项目目录,然后传入绝对路径:finfo_open(FILEINFO_MIME_TYPE, __DIR__ . '/magic') - 改用 PHP 内置的
exif_imagetype()(仅限 JPEG/PNG/GIF/BMP/WEBP 等常见格式,返回整型常量如IMAGETYPE_JPEG),它不依赖外部数据库,但覆盖格式少
别依赖 extension=php_fileinfo.dll 开关——开启只是加载扩展,不解决 magic 数据缺失问题。
真正难的不是调用 finfo_file(),而是理解它只解决“文件头是否像图片”这一个环节;而上传安全是链条,缺一环,前面全白做。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











