必须用 finfo_file() 基于二进制头校验真实 MIME 类型,因 $_FILES[‘type’] 和 validate([‘mime’ => …]) 均可被伪造;需在 move() 前调用 $file->getMime(),并配合魔数校验与多层防护。

仅靠 validate(['ext' => ['jpg', 'png']]) 无法防止 shell.php.jpg 这类文件上传,必须用 finfo_file() 基于二进制头做真实 MIME 校验。
为什么 $_FILES[‘x’][‘type’] 和 validate([‘mime’ => …]) 都不可信
浏览器提交的 $_FILES['file']['type'] 字段可任意伪造,比如把 PHP 文件声明为 image/jpeg;而 ThinkPHP 的 validate(['mime' => 'image/jpeg']) 规则在 6.x 中默认仍依赖该字段(除非你手动触发 getMime()),不是真实探测。真正可靠的是读取临时文件二进制头部——也就是 finfo_file() 的结果。
-
finfo_file()调用系统libmagic数据库,比对文件前若干字节(magic bytes),不受扩展名或 HTTP 头干扰 - 必须用
$_FILES['x']['tmp_name']或$file->getRealPath(),不能对已移动的文件路径调用,否则可能因权限/SELinux 失败 - 若返回
application/octet-stream,不等于“非法”,可能是文件太小、损坏,或 magic db 未覆盖——需配合文件头硬校验兜底
ThinkPHP 中正确调用 finfo_file() 的位置和写法
别在 move() 之后校验,要在 validate() 通过后、move() 之前插入真实 MIME 探测逻辑。ThinkPHP 的 $file->getMime() 就是封装好的 finfo_file(),但前提是 fileinfo 扩展已启用。
- 先确认 PHP 启用了
fileinfo:运行php -m | grep fileinfo,无输出需启用extension=fileinfo - 在控制器中这样写:
$realType = $file->getMime();,它等价于finfo_file($finfo, $file->getRealPath()) - 白名单必须用完整字符串精确匹配:
in_array($realType, ['image/jpeg', 'image/png'], true),不要用strpos($realType, 'image/') === 0 - 若
$realType为空或application/octet-stream,建议再读取前 8 字节做魔数校验(如substr(bin2hex(fread($fp, 8)), 0, 8) === '89504e47'判 PNG)
如何避免 getMime() 返回空或误判
getMime() 返回空或 application/octet-stream 不一定是代码写错了,更可能是上传链路本身出了问题。
立即学习“PHP免费学习笔记(深入)”;
- Nginx 用户检查
client_max_body_size是否过小,导致文件被截断,finfo无法识别 - Apache + mod_security 可能重写了临时文件内容,使 magic bytes 损坏
- Windows 下
tmp_name路径含中文或空格时,finfo_file()会静默失败,建议先move_uploaded_file()到安全路径再探测 - CLI 环境下
$_FILES为空,$file->getMime()必然失败——ThinkPHP 上传类不支持 CLI 模拟 - 某些 Docker 镜像精简掉了
fileinfo,需在构建阶段显式安装并启用
真实 MIME 校验不是“开了 fileinfo 就万事大吉”,它只是五层防线中的一环:前端限制、扩展名白名单、大小控制、二进制头探测、非 Web 目录隔离——漏掉任何一层,都可能被双后缀、空字节或元数据注入绕过。
文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/xitongjiaocheng/124123.html