必须用finfo_file()读取临时文件魔数校验真实MIME类型,因其基于文件头二进制特征识别,不依赖客户端可控的$_FILES['type']或后缀;校验须在moveTo()前完成,且需启用fileinfo扩展。

必须用 finfo_file() 读取临时文件魔数来校验真实 MIME 类型,不能依赖 $_FILES['file']['type'] 或后缀,因为这两者全由客户端控制,极易伪造。
为什么只能用 finfo_file()
finfo_file() 基于文件前几百字节的二进制特征(magic bytes)识别类型,不看文件名、不读 HTTP 头,是 PHP 官方推荐的唯一可靠方式。比如用户上传 shell.php.jpg,后缀是 jpg、Content-Type 声明为 image/jpeg,但 finfo_file() 会直接返回 text/x-php,立刻拦截。
- 必须传入临时文件路径:
$file->getTmpName()或$_FILES['x']['tmp_name'] - 不能传移动后的路径——
moveTo()后再校验就晚了 - 确认 php.ini 中已启用
extension=fileinfo,否则finfo_open()返回 false - 别用
getimagesize()或exif_imagetype()替代:它们对非标准图像支持弱,且可能被构造的 APPx 段绕过
校验必须放在 move 前
ThinkPHP 的 $file->move() 或 moveTo() 是关键分水岭。真实 MIME 校验必须在调用它之前完成,否则恶意文件已落地,删也来不及。
- 错误写法:先
$file->move(),再用 finfo 检查——此时木马已在服务器上 - 正确顺序:获取文件 → finfo 校验 → 通过才重命名并 move
- TP6+ 可用
$file->getMime(),它是 finfo_file() 的封装,但前提是 fileinfo 已启用
怎么集成到 ThinkPHP 验证逻辑中
别把 finfo 校验写在控制器里手动判断,应嵌入验证规则,让框架统一管控。
- TP6 推荐用
rule()(等价于validateRule())挂载到 File 实例 - 用闭包封装 finfo 逻辑,例如只允许真实类型为
image/jpeg或image/png - 避免混用
ext和mimeTypes规则:前者只看后缀,后者只看 $_FILES['type'],两者都不可信 - 示例:
$file->rule(['size' => 2097152, 'callback' => function($f) { /* finfo 校验逻辑 */ }])
校验通过后仍要防“图片马”
即使 finfo 返回 image/jpeg,也不能认为绝对安全——攻击者可能把 PHP 代码追加在图片末尾,或藏在 exif 区域。这时需额外处理:
- 对图片类文件,用
imagecreatefromjpeg()等函数重绘并保存,可剥离隐藏代码 - 上传目录必须隔离在 Web 根目录外(如
runtime/upload/),绝不能放public/uploads/ - Web 服务器需显式禁用脚本执行,Nginx 加
location ~ \.php$ { deny all; } - 强制重命名,丢弃原始文件名,防止双后缀解析(如
1.jpg.php)
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











