php文件上传安全需三层防护:php.ini中upload_max_filesize与post_max_size须合理配置且后者更大,配合finfo_file()校验真实mime类型、pathinfo()提取并小写化扩展名,最后move_uploaded_file()前完成三重校验并将文件存至非web可访问路径。

PHP文件上传的类型和大小限制,不能只靠前端或$_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,上传会直接失败,错误码是UPLOAD_ERR_FORM_SIZE(即3) - 别忘了顺手调高
max_execution_time(至少300)和memory_limit(建议 ≥256M),否则大文件还没读完脚本就超时或爆内存
finfo_file() 是唯一能信的 MIME 类型检查方式
$_FILES['file']['type'] 是浏览器传来的字符串,随手改个 Content-Type: application/x-php 就能绕过。真实类型得看文件头:
- 先确认 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
扩展名检查为什么必须配合 pathinfo() + strtolower()
用户传 shell.php.jpg 或 evil.PHP 很常见,光用 end(explode('.', $_FILES['file']['name'])) 会出错:
- 用
pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION)安全提取扩展名,它自动处理多点、路径干扰 - 立刻
strtolower()统一小写,避免JPG和jpg被当成不同类型 - 白名单必须是完整扩展名数组,比如
['jpg', 'jpeg', 'png', 'pdf'],不要用通配符或正则模糊匹配 - 切记:扩展名检查只是辅助,单独使用毫无意义——攻击者删掉扩展名也能上传恶意内容
move_uploaded_file() 前必须做三重校验,且不能存到 Web 目录
即使前面都做了,最后一步仍可能翻车:
- 三重校验缺一不可:
finfo_file()的 MIME +pathinfo()的扩展名 + 文件大小($_FILES['file']['size'] ≤ $maxSize) - 上传目录不能是
./uploads/这类 Web 可直访路径,应设为/var/www/private_uploads/或类似非公开位置 - 生成新文件名时,彻底丢弃原始名,用
sha256_file($_FILES['file']['tmp_name']) . '.' . $safeExt这类方式,避免路径遍历或空字节注入 - 调用
move_uploaded_file()后,立刻unlink($_FILES['file']['tmp_name'])(虽然通常自动清理,但保险起见)
最容易被跳过的其实是 finfo_open() 的资源释放和上传目录权限控制——没关 $finfo 可能导致后续请求 MIME 检查失效;目录若设成 777,等于给攻击者铺好执行路。安全不是加一层,而是让每层都成为必要关卡。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











