必须用 finfo_file 校验真实 MIME 类型,move 前校验,仅允许 image/jpeg 等安全类型,丢弃原始文件名、生成随机安全名,上传目录须隔离且禁用脚本解析。

光靠 ThinkPHP 自带的后缀白名单(比如 validate(['ext' => 'jpg,png']))根本防不住木马——攻击者把 shell.php 改成 avatar.jpg 就能绕过,甚至在图片里藏 PHP 代码也能执行。真正有效的限制必须从文件内容出发,而不是名字。
必须用 finfo_file 校验真实 MIME 类型
这是唯一可信的校验方式。它读取文件头部“幻数”,识别真实类型,不受浏览器伪造的 $_FILES['file']['type'] 或用户改名影响。
- 校验必须在
$file->move()之前进行,否则临时文件已被删除,finfo_file()会失败 - 只允许明确的安全类型,例如:
image/jpeg、image/png、application/pdf - 拒绝危险类型:
application/x-php、text/html、application/x-sh等 - 别用
getimagesize()替代——它对非图文件报错,且可被构造的 JPEG APPx 段绕过
上传前彻底丢弃原始文件名
用户传来的 shell.php.jpg、../.htaccess、test.php%00.jpg 全部不可信,不能提取扩展名、不能拼路径、不能保留点和斜杠。
- 完全忽略
$_FILES['file']['name'],不调用pathinfo($name, PATHINFO_EXTENSION) - 扩展名由 MIME 类型映射决定:确认是 JPEG 就固定加
.jpg,不是用户说了算 - 生成安全文件名,例如:
uniqid('up_') . bin2hex(random_bytes(8)) . '.jpg' - 提前过滤掉
/、\、..、%00等字符,防止路径遍历或空字节截断
上传目录必须与 Web 访问隔离 + 禁用脚本解析
就算前面全做对了,只要上传文件能被 Web 服务器当 PHP 执行,木马就成功落地。这是最后一道也是最关键的防线。
立即学习“PHP免费学习笔记(深入)”;
- 绝对不要存到
public/uploads/这类可直访目录;应选runtime/upload/或storage/app/uploads/ - 如果必须放 public 下,Nginx 需加规则:
location ~ \.(php|phtml|phar)$ { deny all; } - Apache 推荐配置:
<Directory "/var/www/html/uploads"> php_admin_flag engine off </Directory> - 更彻底的做法:上传目录放在 Web 根目录之外(如
/data/uploads/),所有访问走 PHP 控制器代理
ThinkPHP 中推荐的集成写法
把 finfo 校验封装进 callback 规则,避免逻辑散落在控制器里,也避免先 move 再校验的错误顺序。
- 使用
$file->validate(),而非全局FileValidate实例 - 示例规则:
<?php
use think\File;
$file = request()->file('image');
$file->validate([
'size' => 2097152,
'ext' => ['jpg','jpeg','png','gif'],
'callback' => function ($file) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $file->getTmpName());
finfo_close($finfo);
return in_array($type, ['image/jpeg','image/png','image/gif']);
}
]);
if (!$file->move('uploads')) {
throw new Exception('上传失败:' . $file->getError());
}
不复杂但容易忽略。



















