php文件类型校验必须用服务端白名单,不能依赖前端信息;需同时校验扩展名(strtolower(pathinfo())+显式小写白名单)和真实mime类型(finfo_file()读二进制头),并重命名文件、存至非web可访问目录,配合web服务器禁用危险后缀执行。

PHP文件类型校验必须用服务端白名单,不能依赖前端或浏览器传来的任何信息。核心是“只放行明确允许的类型”,且需从扩展名和真实内容两个层面同时控制。
扩展名白名单必须显式、小写、全覆盖
仅校验后缀名是最基础一环,但极易被绕过——比如上传 shell.PHP、info.php5 或 image.jpg.php。因此:
- 白名单数组要完整列出所有允许的后缀,不写通配符,例如:
['jpg', 'jpeg', 'png', 'gif', 'pdf', 'docx'] - 提取扩展名后统一转小写:
strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)) - 额外拦截危险变体:在比对前先检查原始文件名是否含
php、phtml、phar、htaccess等字符串(大小写不敏感) - 禁止直接使用 ThinkPHP 的
validate(['ext' => 'jpg,png'])作为唯一防线,它只看文件名,不防拼接攻击
MIME 类型白名单必须基于文件头,而非请求头
$_FILES['file']['type'] 完全不可信,它来自浏览器伪造的 Content-Type,curl 一改就失效。真正可靠的只有服务端读取二进制头:
- 启用
fileinfo扩展(上线前用extension_loaded('fileinfo')检查) - 用
finfo_open(FILEINFO_MIME_TYPE)打开,再调用finfo_file()作用于$_FILES['file']['tmp_name'] - 白名单匹配必须是完整 MIME 字符串,如
'image/jpeg',不能只写'jpeg' - 不推荐单独使用
exif_imagetype(),它只读开头几个字节,无法识别“图片+PHP”混合文件
重命名与存储路径是白名单生效的前提
即使类型校验通过,若文件名或存放位置不当,白名单也会形同虚设:
- 上传后必须重命名:用
uniqid() . '.' . $safeExt生成新名,彻底丢弃原始文件名 - 清洗原始名中的点和斜杠:
str_replace(['.', '/', '\'], '_', $_FILES['file']['name']),防止路径遍历或解析歧义 - 上传目录不应放在 Web 可访问路径下(如不要放
public/uploads/),推荐存到runtime/upload/等非公开目录 - 如果必须放 public 下,Nginx/Apache 必须配置规则,禁止执行特定后缀:
location ~ .(php|phtml|phar)$ { deny all; }
Web 服务器配置是最后一道白名单防线
PHP 层校验可被绕过,而 Web 服务器的执行限制无法绕过:
- Nginx 示例:
location ^~ /uploads/ { location ~ .(php|phtml|phar|sh|pl|py)$ { deny all; } } - Apache 示例:在上传目录下放置
.htaccess,写入deny from all,或禁用 PHP 解析:php_flag engine off - 确保上传目录无
Options +ExecCGI或SetHandler类指令,避免意外触发脚本执行
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











