核心是用finfo_file()读取二进制头识别真实mime类型,必须用fileinfo_mime_type模式、校验tmp_name合法性、白名单精确匹配完整字符串,并配合安全重命名与存储隔离。

校验PHP文件内容类型,核心是识别上传文件的**真实内容类型**,而非依赖浏览器提交的 $_FILES['file']['type'] 或文件扩展名。后者极易被伪造,不能作为安全依据。
必须用 finfo_file() 做 MIME 类型探测
finfo_file() 是当前 PHP 中唯一被广泛验证、可靠的内容类型识别方式。它通过读取文件二进制头部(即“魔数”)来判断真实类型,绕过前端干扰。
- 初始化时务必使用
FILEINFO_MIME_TYPE模式,避免FILEINFO_MIME(后者返回带charset的完整字符串,如image/jpeg; charset=binary,比对易出错) - 传入参数必须是
$_FILES['file']['tmp_name'],且需先确认该路径存在、是合法上传临时文件:is_uploaded_file($_FILES['file']['tmp_name']) === true - 上线前检查扩展是否启用:
extension_loaded('fileinfo'),否则直接报错退出
白名单匹配要精确、完整
不允许模糊匹配或截断判断,例如不能用 strpos($mimeType, 'image/') !== false,而应显式限定允许值:
- 接受
'image/jpeg',拒绝'image/jpg'(非标准)、'image/pjpeg'(旧IE遗留,已淘汰) - 接受
'image/png',但不接受'image/x-png'等非标准变体——映射后缀时也必须一一对应,'image/png' => '.png',不可宽泛映射 - 对 SVG 文件,
exif_imagetype()完全无效,必须依赖finfo_file()或额外做 XML 结构扫描
重命名与存储必须彻底隔离原始信息
用户上传 shell.php.jpg,若用 pathinfo($name, PATHINFO_EXTENSION) 提取后缀,就会掉进陷阱。原始文件名一个字节都不能保留。
- 新文件名推荐组合:
uniqid('up_') . bin2hex(random_bytes(8)),再拼上由 MIME 映射来的固定后缀(如'image/webp' → '.webp') - 文件名清洗不是首选方案;若必须处理,仅允许字母、数字、点、下划线、短横线:
preg_replace('/[^a-zA-Z0-9._-]/', '', $basename) - 上传目录禁止执行权限(如 Apache 配置
php_flag engine off),并和 Web 可访问路径物理分离(如存到/data/uploads/而非./public/uploads/)
可选增强:魔数初筛 + 编码验证(按需叠加)
对极高安全要求场景,可在 finfo 校验前后补充轻量级防护:
- 快速初筛可用
exif_imagetype(),但仅限图像类且必须作为第一道关卡——它无法识别拼接型恶意文件(如cat good.jpg bad.php > evil.jpg),绝不可单独使用 - 若业务需判断是否为纯文本(如日志、配置文件),在
finfo_file()返回text/*后,再用mb_check_encoding(file_get_contents($tmp, false, null, 0, 65536), $encoding)验证前 64KB 字节编码有效性 - 空文件会被判为
application/octet-stream,需单独用filesize($tmp) === 0判断并按策略处理
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











