应使用 finfo_file() 校验文件真实 MIME 类型,因其直接解析二进制头,比不可信的 $_FILES['type'] 和有盲区的 exif_imagetype() 更可靠;须配合严格重命名、Web 根外存储及目录执行禁用。

只靠 $_FILES['file']['type'] 或后缀名判断文件类型,等于把服务器大门钥匙交给上传者——它根本不可信。
finfo_file 为什么比 $_FILES['type'] 可靠得多
$_FILES['file']['type'] 是浏览器提交的 Content-Type 头,攻击者用 curl 或 Burp Suite 一改就变;而 finfo_file() 直接读取临时文件的二进制头,识别真实 MIME 类型,绕过成本高得多。
- 必须用
finfo_open(FILEINFO_MIME_TYPE),不能用FILEINFO_MIME(后者返回带编码的完整字符串,如image/jpeg; charset=binary,比对易出错) - 调用前要检查
$_FILES['file']['tmp_name']是否存在且是合法上传临时文件,避免空值或路径遍历传参导致finfo_file读取任意文件 - 白名单必须是完整 MIME 字符串,例如
'image/jpeg',不能只写'jpeg'或'jpg' - 某些老旧 PHP 环境可能未启用 fileinfo 扩展,上线前务必执行
extension_loaded('fileinfo')检查
exif_imagetype() 的盲区:它只看开头几个字节
exif_imagetype() 快、轻量,但仅校验文件头魔数(如 JPEG 的 FF D8 FF),无法发现“图片+PHP 脚本”的拼接文件。攻击者用 cat good.jpg bad.php > evil.jpg 就能绕过。
- 它不支持 PDF、SVG、WebP 等非图像格式,返回
false不代表非法,可能是合法但不支持的类型 - 对 GIF87a/GIF89a 兼容,但遇到带注释块或扩展块的 GIF 可能误判
- 若业务需支持 SVG,
exif_imagetype()完全无效,必须回退到finfo_file或 XML 结构解析 - 不要把它当唯一校验手段,最多作为快速初筛,后面仍需
finfo_file或内容扫描兜底
重命名时强制扩展名,不是“保留原后缀”
用户上传 shell.php.jpg,你若用 pathinfo($name, PATHINFO_EXTENSION) 提取后缀,就会得到 jpg——这正是攻击者设计的陷阱。
立即学习“PHP免费学习笔记(深入)”;
- 丢弃
$_FILES['file']['name']的全部内容,一个字符都别留 - 生成新文件名用
uniqid('up_') . bin2hex(random_bytes(8)),然后拼上由 MIME 映射来的固定后缀,例如image/png → '.png' - 映射表必须严格一一对应:
'image/gif' => '.gif',不能把'image/x-png'也映射成'.png'(这是非标准 MIME,应拒绝) - 文件名里禁止出现
..、/、\0、控制字符,可用preg_replace('/[^a-zA-Z0-9._-]/', '', $basename)清洗,但更推荐彻底不用原始名
上传目录必须脱离 Web 根,且禁用脚本解析
即使文件名和类型都安全,只要上传目录在 Web 可访问路径下(如 /var/www/html/uploads/),Apache/Nginx 仍可能因配置疏漏执行 .php、.phtml、.phar 文件。
- 理想路径是 Web 根外,如
/data/uploads/,再通过代理脚本(如fetch_image.php)读取并输出,这样连.htaccess都失效 - 若必须放 Web 目录内,至少加
.htaccess(Apache)或location ~ \.(php|sh|pl|py|rb)$ { deny all; }(Nginx)禁用执行 - 注意 Nginx 的
fastcgi_split_path_info配置漏洞:恶意请求/uploads/image.jpg/xxx.php可能被错误解析为 PHP 脚本 - Linux 下给上传目录设权限
chmod 755 /data/uploads,属主设为 web 进程用户(如www-data),但禁止写入父目录
真正危险的不是“不会写校验逻辑”,而是把多个半吊子措施拼在一起——比如用了 finfo_file 却没重命名,或重命名了却把目录放在 Web 下。每个环节都要独立验证、独立失效,不能指望前一步挡住所有攻击。



















