$_files'file'不可信,因其由浏览器伪造;应忽略该字段,改用finfo_file()服务端解析mime;后缀校验须显式白名单、统一小写、重命名文件并禁用原始名中的点和斜杠;上传目录须配置web服务器禁止执行php。

为什么 $_FILES['file']['type'] 不能信
浏览器传来的 $_FILES['file']['type'] 是前端自填的,随便改个 text/plain 就能绕过。它只是 HTTP 请求头里的 Content-Type,服务端没做任何校验就直接用,等于把门锁换成贴纸。
实操建议:
- 彻底忽略
$_FILES['file']['type'],不参与任何安全判断 - 真要查 MIME,必须用服务端解析(如
finfo_file()),且只用于辅助识别,不作为唯一依据 - ThinkPHP 自带的
validate(['ext' => 'jpg,png'])默认只校验后缀,和type无关,别混淆
后缀白名单必须在服务端硬校验,且区分大小写
用户上传 shell.PHP 或 .phtml、.php5 这类变体,光过滤 .php 就漏了。ThinkPHP 的 ext 验证默认不区分大小写,但 Web 服务器(如 Nginx/Apache)可能区分——比如 .PHP 在某些配置下仍可执行。
实操建议:
- 白名单显式写出所有允许后缀:
['jpg', 'jpeg', 'png', 'gif'],不写通配符 - 用
strtolower(pathinfo($file['name'], PATHINFO_EXTENSION))统一小写再比对 - 额外拦截危险扩展名:
php、phtml、php3、php4、php5、phar、htaccess - 注意 ThinkPHP 6 的
validate(['ext' => '...'])是基于文件名后缀,不是内容,所以必须配合重命名(见下一条)
上传后必须重命名,且禁用原始文件名中的点和斜杠
攻击者传 shell.php.jpg,你只校验后缀是 jpg 就放行,但 Apache 可能按从右往左规则解析成 PHP;更糟的是传 ../../.htaccess 直接覆盖配置。
实操建议:
- 强制使用
uniqid() . '.' . $safeExt生成新文件名,丢弃原始$file['name'] - 重命名前先用
str_replace(['.', '/','\'], '_', $filename)清洗,避免路径遍历 - ThinkPHP 6 的
moveTo()方法接受目标路径,务必传入绝对路径,且确保目录不在 Web 可访问范围内(如放到runtime/upload/而非public/uploads/) - 如果必须放在
public/下,Nginx 要加location ~ .(php|phtml|phar)$ { deny all; }
光靠 PHP 层校验不够,Web 服务器配置才是最后一道墙
ThinkPHP 再怎么校验,只要文件最终落在 Web 根目录下,且服务器没限制,一个漏掉的 .pht 或被解析的 .jpg?.php(通过查询参数触发)都可能执行代码。
实操建议:
- 上传目录禁止执行 PHP:Nginx 中用
location ^~ /uploads/ { location ~ .(php|phtml|phar) { return 404; } } - Apache 中在上传目录放
.htaccess:php_flag engine off或RemoveHandler .php - Linux 权限设为
644,目录755,确保 Web 用户无写+执行权限 - 不要依赖
disable_functions拦system()—— 真正的漏洞是文件被当 PHP 解析,不是函数调用
最易被忽略的一点:ThinkPHP 的验证规则跑在 PHP 层,而 Web 服务器的 MIME 处理、路径解析、扩展名匹配是另一套逻辑。两层之间永远有缝隙,堵漏得两边一起动刀,不能只信框架返回的“验证通过”。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











