类型验证必须用finfo_file()检查真实MIME,不能信$_FILES'file';大小限制必须在php.ini和代码里各做一次,缺一不可。

直接说结论:类型验证必须用 finfo_file() 检查真实 MIME,不能信 $_FILES['file']['type'];大小限制必须在 php.ini 和代码里各做一次,缺一不可。
php.ini 里 upload_max_filesize 和 post_max_size 怎么配才不翻车
这两个值不是随便改大就行,它们有硬性依赖关系:
-
upload_max_filesize控制单个文件上限,比如设为20M -
post_max_size必须大于upload_max_filesize,还要预留普通表单字段空间,建议设为25M或更高 - 只改
upload_max_filesize却忽略post_max_size,上传会直接失败,$_FILES['file']['error']返回UPLOAD_ERR_FORM_SIZE(值为 3) - 改完必须重启 PHP-FPM 或 Apache,
nginx -s reload不管用 - 用
ini_get('upload_max_filesize')确认真实生效值,别只看配置文件
finfo_file() 怎么用才真正校验到文件类型
$_FILES['file']['type'] 是浏览器传的字符串,改个 Content-Type 就能绕过。真实类型得看文件头:
- 先确认 PHP 启用了
fileinfo扩展:php -m | grep fileinfo - 用
finfo_open(FILEINFO_MIME_TYPE)创建资源,再调finfo_file($finfo, $_FILES['file']['tmp_name']) - 返回的是纯 MIME 字符串,如
image/jpeg,必须和白名单严格比对(不用strpos,防伪造) -
finfo_file()读的是临时文件,$_FILES['file']['tmp_name']必须存在且可读,否则返回false - 白名单写成
['image/jpeg', 'image/png', 'application/pdf']这种完整字符串,别用通配符
扩展名提取为什么不能用 explode('.', $filename)
用户传 shell.php.jpg 或 evil.PHP 很常见,靠字符串分割会出错:
立即学习“PHP免费学习笔记(深入)”;
- 用
pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)安全提取扩展名,它自动处理多点、路径干扰 - 立刻
strtolower()统一小写,避免JPG和jpg被当成不同类型 - 白名单必须是完整扩展名数组,比如
['jpg', 'jpeg', 'png', 'pdf'] - 注意:扩展名检查只是辅助,单独使用毫无意义——攻击者删掉扩展名也能上传恶意内容
move_uploaded_file() 前必须做的三重校验
即使前面都做了,最后一步仍可能翻车:
- 三重校验缺一不可:
finfo_file()的 MIME +pathinfo()的扩展名 + 文件大小($_FILES['file']['size'] ≤ $maxSize) - 目标目录必须存在且 PHP 进程有写权限,路径别用相对路径,建议用绝对路径或
__DIR__ . '/uploads/' - 上传文件绝不能存到 Web 可访问路径,比如不要放在
public/或htdocs/下 - 生成唯一文件名(如
md5_file($_FILES['file']['tmp_name']) . time()),避免覆盖和猜测
最容易被忽略的点是:finfo_file() 返回的 MIME 类型和白名单比对时,必须完全一致,连空格都不能多一个;还有就是 move_uploaded_file() 的目标路径权限问题,很多线上故障就卡在这一步,报错却没日志,只能靠 is_writable() 提前检查。



















