$_files'file'不可靠,需用finfo_open()读取文件头识别真实mime类型并白名单校验,同时严格检查小写扩展名,accept属性仅前端提示,不能替代服务端验证。

PHP 表单中用 $_FILES['file']['type'] 判断 MIME 类型不可靠
浏览器提交的 $_FILES['file']['type'] 是前端伪造的,仅由文件扩展名推断而来,完全可被绕过。比如把 evil.php 改名为 image.jpg,它就会显示为 image/jpeg,但实际仍是 PHP 文件。真实场景中,仅靠这个字段做判断等于没验证。
正确做法是结合服务端文件内容检测:
- 用
finfo_open()读取二进制头(magic bytes)识别真实 MIME 类型 - 配合白名单校验,只允许如
image/png、application/pdf等明确类型 - 同时检查扩展名是否匹配(防止 .htaccess 或 .user.ini 类型绕过)
用 finfo_file() 获取真实 MIME 类型并校验
这是目前最通用、兼容性好的方式。注意必须用 FILEINFO_MIME_TYPE 模式,避免返回带字符集的完整字符串(如 text/plain; charset=utf-8)。
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);
$allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
if (!in_array($mimeType, $allowedTypes, true)) {
die('不支持的文件类型');
}
常见坑:
立即学习“PHP免费学习笔记(深入)”;
- 未开启
fileinfo扩展(PHP 默认可能关闭),需确认phpinfo()中存在fileinfo support => enabled - 上传大文件时
$_FILES['file']['tmp_name']可能为空,要先检查$_FILES['file']['error'] === UPLOAD_ERR_OK - 某些 PDF 文件可能被识别为
application/x-empty,需额外 fallback 处理
扩展名二次校验不能只看 pathinfo($filename, PATHINFO_EXTENSION)
用户上传 shell.php.jpg 时,pathinfo() 会返回 jpg,看似安全,但 Apache 等服务器可能按从右到左顺序解析,仍执行 PHP。所以必须提取「最后一个点之后」的扩展名,并统一转小写比对。
$ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
$allowedExts = ['jpg', 'jpeg', 'png', 'pdf'];
if (!in_array($ext, $allowedExts, true)) {
die('扩展名不合法');
}
更稳妥的做法是:生成新文件名,强制使用根据 MIME 类型推导出的扩展名(如 image/jpeg → .jpg),彻底丢弃原始文件名。
HTML 表单里的 accept 属性只是提示,不是限制
<input type="file" accept=".jpg,.png,.pdf"> 仅影响浏览器文件选择对话框的过滤选项,无法阻止用户手动输入或拖入非法文件,也不能替代服务端验证。
真正起作用的只有服务端逻辑。如果发现“明明写了 accept 还能传 php”,这不是 bug,是预期行为。
复杂点在于:不同浏览器对 accept 的实现不一致(比如 Safari 对 MIME 类型支持较弱),而攻击者根本不会走这个 UI —— 他们直接发 POST 请求或用 curl 上传。所以只要服务端验证没做牢,前端所有限制都形同虚设。











