finfo_file()是php中唯一能从文件二进制头判断真实mime类型的可靠方法,需用fileinfo_mime_type、校验tmp_name合法性、白名单匹配完整类型,并对text/*文件额外验证编码有效性。

用 finfo_file() 识别真实 MIME 类型
finfo_file() 是 PHP 中唯一能从文件二进制头判断真实类型的可靠方法。它不依赖浏览器传来的 $_FILES['file']['type'](这个值可被 curl 或 Burp 任意篡改),而是直接读取临时上传文件 $_FILES['file']['tmp_name'] 的原始字节。
关键操作要点:
- 必须调用 finfo_open(FILEINFO_MIME_TYPE),不能用 FILEINFO_MIME(后者返回带 charset 的完整字符串,如
text/plain; charset=utf-8,比对易出错) - 调用前先确认 $_FILES['file']['tmp_name'] 存在且为合法临时文件路径,防止空值或路径遍历导致读取任意系统文件
- 白名单应匹配完整 MIME 字符串,例如
'image/jpeg'、'text/csv',不能只写'jpeg'或'csv' - 上线前检查扩展是否启用:extension_loaded('fileinfo'),未启用会静默失败
文本文件需额外验证编码有效性
MIME 类型为 text/* 只代表“声称是文本”,不代表内容真能安全解析。一个被标记为 text/plain 的文件,可能实际是乱码二进制或含非法 UTF-8 字节。
推荐组合验证方式:
- 先用 finfo_file() 过滤掉明显非文本类型(如
application/pdf、image/png) - 对
text/*类型文件,读取前 64KB 内容,用 mb_detect_encoding($content, ['UTF-8','GB2312','BIG5','ISO-8859-1'], true) 检测编码(true表示严格模式) - 再用 mb_check_encoding($content, $encoding) 二次确认该编码下内容是否合法
- 空文件(
filesize() === 0)会被判为application/octet-stream,需单独处理
别信文件名和 exif_imagetype()
用户上传的 shell.php.jpg,若用 pathinfo($name, PATHINFO_EXTENSION) 提取后缀,会得到 jpg —— 这正是攻击者设计的陷阱。原始文件名一个字符都不能保留。
exif_imagetype() 仅检查文件开头几个字节的魔数(如 JPEG 的 FF D8 FF),存在严重局限:
- 无法识别“图片+PHP 脚本”拼接文件(
cat good.jpg bad.php > evil.jpg即可绕过) - 对 SVG 完全无效(不解析 XML 结构)
- 遇到 GIF 注释块或扩展块可能误判
- 返回
false不一定非法,可能是合法但不支持的格式
它只能作为第一道快速初筛,绝不可单独用于安全校验。
安全重命名与存储隔离
校验通过后,文件必须彻底丢弃原始名称,生成全新、不可预测的文件名:
- 推荐格式:uniqid('up_') . bin2hex(random_bytes(8)) + 由 MIME 映射出的固定后缀(如
image/png → '.png') - 映射表必须严格一一对应:
'image/gif' => '.gif',拒绝非标准 MIME(如'image/x-png') - 文件名中禁止出现
..、/、\0或控制字符;清洗不如彻底不用 - 上传目录必须与 Web 可访问目录物理隔离,或通过 Apache/Nginx 配置禁止执行 PHP(如
php_flag engine off)
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











