PHP 8 文件上传校验需三步闭环:先验 $_FILES'file' 确保上传成功并取 tmp_name;再用 finfo 基于二进制识别真实 MIME 并白名单比对;最后魔数校验+重命名+隔离存储+禁脚本执行。

PHP 8 上传文件并校验 MIME,关键不是“检查一下 type 字段”,而是用真实内容识别 + 白名单控制 + 安全落地三步闭环。浏览器传来的 $_FILES['file']['type'] 完全可伪造,必须丢弃不用。
第一步:确认上传成功并获取临时文件路径
先确保文件真正上传到位,再操作:
- 检查
$_FILES['file']['error'] === UPLOAD_ERR_OK,排除无文件、超限、临时目录失败等基础错误 - 只信任
$_FILES['file']['tmp_name']—— 这是 PHP 写入的临时路径,唯一可用于后续检测和移动 - 忽略
$_FILES['file']['name']中的原始扩展名和完整文件名,它不具可信度
第二步:用 finfo 精确提取真实 MIME 类型
这是校验核心,必须基于二进制内容而非后缀或 header:
- 调用
$finfo = finfo_open(FILEINFO_MIME_TYPE)初始化检测器 - 传入临时文件路径:
$realType = finfo_file($finfo, $_FILES['file']['tmp_name']) - 比对白名单,例如:
in_array($realType, ['image/jpeg', 'image/png', 'application/pdf'], true) - 严格拒绝
text/html、application/x-php、application/x-sh等一切可执行/可解析类型
第三步:补充魔数校验(防“图片马”绕过)
finfo 有时会被构造精巧的混合文件欺骗,加一层文件头(魔数)校验更稳妥:
立即学习“PHP免费学习笔记(深入)”;
- 用
fopen($tmp_name, 'rb')打开临时文件,fread($handle, 16)读前 16 字节 - 用
unpack('H*', $bin)转为十六进制字符串,提取开头字节比对标准魔数 - 例如 JPG 必须以
'ffd8ff'开头,PNG 必须含'89504e47',PDF 必须以'25504446'(%PDF)起始 - 魔数不匹配直接中止,不给后续处理机会
第四步:安全落地——重命名 + 隔离存储 + 禁脚本执行
即使类型校验通过,也绝不能原样保存或放 Web 可访问目录:
- 生成唯一文件名(如
uniqid() . '_' . random_int(1000, 9999) . '.jpg'),彻底脱离用户输入 - 上传目录必须脱离 Web 根目录(如
/var/www/uploads/而非./uploads/) - 若必须放在 Web 目录下,需在 Web 服务器(Nginx/Apache)配置中禁用该目录下的脚本执行权限
- 最后调用
move_uploaded_file($tmp_name, $safePath)移动,禁止用copy()或rename()



















