应使用 finfo_file() 替代已废弃的 mime_content_type(),因其基于现代 libmagic 更可靠,需显式创建 finfo 实例并传入文件路径;$_files'x' 完全不可信,必须用 finfo_file() 检测临时文件内容,小文件可用 finfo_buffer() 但需限制大小,mime 判定本质是启发式猜测,须结合白名单、内容扫描与执行隔离三重防护。

用 finfo_file() 而不是 mime_content_type()
PHP 自带的 mime_content_type() 已被标记为废弃(PHP 8.0+ 直接移除),它依赖系统命令或过时的魔数库,结果不稳定,尤其对无扩展名、内容被截断或压缩过的文件容易误判。而 finfo_file() 是 libmagic 的现代封装,更可靠,且支持自定义 magic 数据库。
- 必须显式创建
finfo实例:finfo_open(FILEINFO_MIME_TYPE),不能直接调用函数 - 传入完整文件路径(
finfo_file()不接受资源句柄或字符串内容) - 若文件不可读或路径不存在,返回
false,需手动检查并处理 - 示例:
$finfo = finfo_open(FILEINFO_MIME_TYPE);<br>if ($finfo) {<br> $mime = finfo_file($finfo, '/path/to/file.jpg');<br> finfo_close($finfo);<br>}
上传文件时别信 $_FILES['x']['type']
浏览器提交的 $_FILES['x']['type'] 是客户端自填字段,可任意伪造,完全不可信。曾有用户上传 .php 文件却报 image/jpeg,导致绕过前端校验后执行恶意代码。
- 仅用于快速过滤(比如跳过明显非图类请求),但绝不作为安全依据
- 必须配合服务端真实内容检测(即
finfo_file())再做判断 - 注意:上传临时文件路径(
$_FILES['x']['tmp_name'])才是finfo_file()的合法输入,不是原始文件名
常见 MIME 判定陷阱与绕过场景
即使用了 finfo_file(),仍可能因文件构造特殊而误判——这不是 PHP 的 bug,而是魔数识别本身的局限性。
- 空文件或极短内容(如只有 1 字节)常被识别为
application/octet-stream,需额外长度检查 - ZIP/PDF/DOCX 等容器格式,头部被篡改或加壳后可能识别失败,建议结合文件扩展名做二级 fallback(但仅限白名单内扩展)
- 图片隐写术(如在 JPG 后追加 PHP 代码)通常不影响
finfo对主体类型的识别,但实际执行时风险仍在;MIME 检查不能替代内容扫描或沙箱执行 - 某些旧版 libmagic(如 CentOS 6 默认)不识别 WebP 或 AVIF,升级系统 magic 库或指定自定义
magic文件路径可缓解
小文件 & 内存限制下的替代方案
当无法访问磁盘(如 S3 临时流)或要避免写临时文件时,finfo_buffer() 可直接分析二进制内容,但要注意内存和性能边界。
立即学习“PHP免费学习笔记(深入)”;
-
finfo_buffer()接收字符串,适合已读入内存的小文件(建议 ≤ 2MB) - 大文件用
finfo_buffer()容易 OOM,且只读开头几 KB,识别精度下降 - 若只能用流,先用
fgets()或stream_get_contents($fp, 8192)读前 8KB 再传给finfo_buffer(),比全量读取更稳妥 - 示例:
$head = file_get_contents($_FILES['x']['tmp_name'], false, null, 0, 8192);<br>$mime = finfo_buffer($finfo, $head, FILEINFO_MIME_TYPE);











