必须用 getclientmimetype() 获取真实 mime 类型并结合 fileinfo 校验文件头 magic number,再三重过滤扩展名,缺一不可。

在Laravel中防止用户上传伪装成图片的恶意PHP脚本,必须绕过文件扩展名陷阱,直接读取文件真实内容特征——这要求你用 getClientMimeType() 获取服务端解析出的 MIME 类型,并用 fileinfo 手动校验文件头字节,二者缺一不可。
用 UploadedFile 获取真实 MIME 类型
第一步:在控制器中通过 $request->file('xxx') 获取上传实例,不要用 File::type() 或 pathinfo() 提取扩展名。
第二步:对 UploadedFile 对象调用 【getClientMimeType()】 方法,它底层调用 PHP 的 finfo_file(),基于文件前几百字节识别类型,返回 image/jpeg、application/pdf 等标准字符串。
第三步:立刻丢弃 $file->getClientOriginalExtension() 的结果——这个值由浏览器提交,完全可被篡改,仅作日志记录用,绝不能用于安全判断。
手动检测文件头 Magic Number(绕过 getClientMimeType 限制)
当上传 .bdoc、.asice 等非标准格式时,getClientMimeType() 可能返回空或 application/octet-stream,此时需直读文件头字节比对 Magic Number。
方法一:用 finfo_buffer() 检测临时文件内容
先调用 $file->getRealPath() 获取临时文件绝对路径 → 用 file_get_contents() 读取前 16 字节 → 传入 finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $bytes) 获取类型。
方法二:跳过临时文件,直接处理上传流
用 $file->openFile('r') 获取 SplFileObject → 调用 fread($handle, 16) 读取头部 → 用 bin2hex() 转为十六进制字符串 → 与已知 Magic Number 表比对(如 PDF 固定为 25504446,PNG 为 89504e47)。
【注意:finfo_buffer() 必须传入原始二进制字节,不能传 base64 解码后的字符串,否则识别失败】
组合校验:MIME + 文件头 + 扩展名三重过滤
① 先用 getClientMimeType() 初筛,排除明显非法类型(如 text/html、application/x-php)。
② 对初筛通过的文件,再用 finfo_buffer() 重检文件头,确认是否匹配声称的 MIME 类型——例如 MIME 是 image/png,但文件头不是 89504e47,则拒绝。
③ 最后检查扩展名是否在白名单内(如 png/jpg/pdf),且与 MIME 类型逻辑一致(image/* 对应图片扩展名,application/* 对应文档扩展名)。
这三步必须全部通过才允许保存,任一环节失败立即 return response()->json(['error' => 'Invalid file signature'], 422)。











