必须用finfo_open()读取临时文件魔数校验真实MIME类型,因扩展名和$_FILES['type']均可伪造;需配合is_uploaded_file()验证上传来源、白名单比对、强制重命名及隔离存储。

PHP 8.4 中校验上传文件的真实类型,不能只看扩展名或 $_FILES['type'](它由浏览器提交,极易伪造),必须依赖文件内容本身。核心方法是结合 文件魔数(header bytes) 和 finfo 扩展的 MIME 类型识别,两者互补验证最稳妥。
用 finfo 获取真实 MIME 类型(首选基础手段)
finfo 是 PHP 内置且默认启用的扩展,在 PHP 8.4 中依然稳定可靠。它通过读取文件二进制头并比对系统 magic 数据库来判断类型,精度高、开销低。
- 先确认 finfo 可用:
extension_loaded('fileinfo') - 用
finfo_open(FILEINFO_MIME_TYPE)打开检测器,传入临时文件路径:$_FILES['file']['tmp_name'] - 获取结果后严格比对白名单,例如只允许:
image/jpeg、image/png、application/pdf、text/plain - 注意:不要用
mime_content_type(),它在 PHP 8.4 中已被标记为 deprecated,官方推荐统一走 finfo
读取文件头字节校验魔数(增强防伪装能力)
某些极端场景(如攻击者精心构造绕过 magic 库的文件),仅靠 finfo 可能存在极小概率误判。此时手动读取前 4–16 字节转十六进制,与标准魔数比对,可作为第二道防线。
修复 Core bailout 状态损坏安全问题,以及 BZ2、DOM、PDO、Opcache/JIT 内存与执行错误,改进 Unicode 字素处理、URL 解析、TLS 流状态、信号分发及会话错误处理。
- 用
fopen($tmp_path, 'rb')以二进制方式打开临时文件 -
fread($fp, 16)读取开头字节,再用unpack('H*', $bin)转成十六进制字符串 - 常见魔数示例:
'ffd8ff'(JPEG)、'89504e47'(PNG)、'25504446'(PDF)、'47494638'(GIF) - 建议至少校验前 4 字节(足够区分主流格式),避免过度读取影响性能
配合关键安全动作,缺一不可
类型校验只是起点,必须和其它措施联动才真正有效:
立即学习“PHP免费学习笔记(深入)”;
- 调用
is_uploaded_file($_FILES['file']['tmp_name'])确保文件来自本次 HTTP 上传,而非本地路径伪造 - 检查
$_FILES['file']['error'] === UPLOAD_ERR_OK,排除传输中断、超限等错误状态 - 限制大小:前端 +
$_FILES['file']['size']双重判断,PHP 8.4 默认仍受upload_max_filesize约束 - 重命名文件:丢弃原始文件名,用
uniqid() . '.' . $safe_ext生成新名,扩展名必须从可信来源(如 finfo 结果)推导,不可信任pathinfo($name, PATHINFO_EXTENSION) - 上传目录禁止执行权限(Linux 下
chmod -x uploads/),且不放在 Web 可直接访问的路径下
不推荐也不再安全的做法
以下方式在 PHP 8.4 环境中已不适用或风险极高,应立即停用:
- 依赖
$_FILES['file']['type']字段做判断 —— 客户端完全可控,形同虚设 - 仅用
pathinfo($filename, PATHINFO_EXTENSION)判断类型 —— 后缀可任意修改,毫无防护力 - 用
getimagesize()判定图片 —— 对非图像文件返回 false 或警告,无法用于 PDF/视频等,且不防恶意嵌入 - 使用已废弃的
mime_content_type()—— PHP 8.4 发出弃用警告,未来版本将移除


















