PHP 8.1 必须用 finfo_open 校验文件头(Magic Bytes)识别真实类型,仅靠后缀或 $_FILES['type'] 不安全;需在 move_uploaded_file 前白名单校验 MIME,再手动比对前若干字节。

PHP 8.1 中仅靠文件名后缀或 $_FILES['type'] 根本拦不住恶意脚本,必须用文件头(Magic Bytes)做内容级校验。这是绕过所有后缀伪装(如 shell.php.jpg、webshell.PHP、1.php%00.png)最可靠的一环。
用 finfo_open 读取真实 MIME 类型
PHP 默认启用 fileinfo 扩展(Docker 镜像常禁用,需先确认 extension=fileinfo 已开启)。关键操作必须在 move_uploaded_file() 或 ThinkPHP 的 moveTo() 之前完成:
- 调用
finfo_open(FILEINFO_MIME_TYPE)获取文件真实类型,不是靠扩展名猜 - 只接受明确白名单值,例如:
image/jpeg、image/png、application/pdf - 严格拒绝:
text/plain、application/x-php、application/x-sh、text/html - 不要用
getimagesize():对非图文件会报错,且 JPEG 中的 APPx 段可被用来绕过 - 别只判断是否含
image/前缀——image/php这类伪造类型必须被精准识别并拦截
手动校验文件头字节(补充加固)
对于高敏感场景(如 CMS 后台、用户头像上传),可在 MIME 校验基础上加一层原始字节比对:
修复 Core bailout 状态损坏安全问题,以及 BZ2、DOM、PDO、Opcache/JIT 内存与执行错误,改进 Unicode 字素处理、URL 解析、TLS 流状态、信号分发及会话错误处理。
- 用
fopen($tmp_name, 'rb')读取前 4–16 字节 - 匹配典型 Magic Bytes,例如:
GIF89a→ GIF(ASCII 十六进制47 49 46 38 39 61)\xff\xd8\xff→ JPEG(二进制开头)%PDF→ PDF(ASCII) - 不依赖正则模糊匹配,直接用
bindec()或unpack('H*')提取并比对固定字节序列 - 若文件头与声称的 MIME 类型明显矛盾(如 MIME 是
image/png但开头是<?php),立即中止
配合其他必要措施才真正有效
文件头校验不是“一招鲜”,它必须嵌入完整防护链:
立即学习“PHP免费学习笔记(深入)”;
- 后缀校验只作初筛:显式白名单(
['jpg','png','pdf']),提取后强制小写,同时额外拦截含php、phtml、phar的原始文件名 - 上传后立刻重命名:丢弃全部原始文件名,用
uniqid() . '_' . bin2hex(random_bytes(8))生成新名,并按 MIME 映射固定扩展名(如image/gif → '.gif') - 存储路径隔离:上传目录必须脱离 Web 根目录,或在 Web 服务器(Nginx/Apache)中配置
location ~ \.php$ { deny all; }禁止执行 - 大小与数量双控:
php.ini中设upload_max_filesize和post_max_size,脚本内再做二次检查
不复杂但容易忽略。文件头校验本身几行代码就能写完,但只有和重命名、路径隔离、MIME 白名单一起用,才能把恶意脚本真正挡在门外。


















