安全文件上传需严格校验:先检查$_FILES'file'===UPLOAD_ERR_OK和is_uploaded_file(),再用finfo_open()检测真实MIME类型并白名单验证,重命名文件、存至Web不可达目录,配合Nginx/Apache禁用执行权限。

PHP 文件上传本身很简单,但「安全上传」不是加个 is_uploaded_file() 就完事的——绕过校验、任意文件执行、临时文件竞争、路径遍历、MIME 伪造,每一步都可能被利用。
检查 $_FILES 结构和基础状态
别急着移动文件,先确认上传是否真实发生、有没有报错。PHP 不会自动拒绝恶意文件,它只管把二进制塞进临时目录。
必须检查两个关键字段:
-
$_FILES['file']['error'] === UPLOAD_ERR_OK(不是0字面量,用常量) -
is_uploaded_file($_FILES['file']['tmp_name'])(防止本地文件路径伪造)
漏掉任一检查,攻击者可传入 /etc/passwd 这类路径触发 move_uploaded_file() 的文件读取漏洞。
不信任客户端传来的任何元信息
$_FILES['file']['name']、$_FILES['file']['type'] 全由浏览器控制,可任意篡改。用 finfo_open(FILEINFO_MIME_TYPE) 重新检测内容类型,且仅用于辅助判断,不能作为唯一依据。
示例做法:
$finfo = finfo_open(FILEINFO_MIME_TYPE); $mimeType = finfo_file($finfo, $_FILES['file']['tmp_name']); finfo_close($finfo); // 只允许 'image/jpeg'、'image/png' 等白名单值,且需结合后缀+内容双重验证
注意:getimagesize() 对非图像文件可能抛出警告,exif_imagetype() 更轻量但仅支持图像;不要依赖 pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION) 做后缀判断——shell.php.jpg 会被识别为 jpg。
重命名 + 存储到非 Web 可访问目录
上传后的文件名必须彻底重写,用 random_bytes(16) + bin2hex() 生成唯一名,再拼上严格校验后的扩展名(从白名单映射,不从原始名提取)。
存储路径必须脱离 Web 根目录,例如:
- Web 根目录是
/var/www/html - 上传目录设为
/var/www/uploads(同级),并确保该目录无open_basedir漏洞或 ApacheAlias暴露 - 若必须通过 Web 访问,用 PHP 脚本代理(如
download.php?id=abc123),禁止直接暴露文件路径
临时文件 $_FILES['file']['tmp_name'] 在脚本结束时自动销毁,但若上传大文件或并发高,可能残留;不要在 move_uploaded_file() 前做耗时操作(如远程校验),避免临时文件被竞争利用。
补充硬性防护点
光靠 PHP 层不够,服务器配置是最后一道防线:
- Nginx:在上传目录 location 块中加
deny all;,或限制只允许GET且禁用exec - Apache:上传目录下放
.htaccess,内容为Deny from all,并确认AllowOverride All生效 - PHP 配置:确保
file_uploads = On,但upload_max_filesize和post_max_size合理(如 8M),避免 DoS - SELinux / AppArmor:限制 PHP 进程对上传目录的写权限,仅允许
httpd_t或www-data写入
最常被忽略的是:没关掉 display_errors,导致错误信息泄露临时路径;或者用 extract($_FILES) 这类危险操作,把攻击者控制的变量直接注入作用域。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











