thinkphp 仅靠后缀白名单无法拦截恶意文件,因$_files'file'和['type']均可伪造;必须启用fileinfo扩展并用validatetype()校验真实mime类型,同时上传目录须隔离于webroot外并禁用脚本执行。

ThinkPHP 上传文件后缀校验为什么拦不住恶意文件
因为 $_FILES['file']['name'] 的后缀可被伪造,仅靠 ext 配置项(如 'ext' => 'jpg,png,gif')做后缀白名单,完全无效。攻击者上传 shell.php.jpg 或直接改包为 webshell.php 但声明 Content-Type: image/jpeg,TP 默认不校验真实内容。
- TP 5.1+ 的
validateExt()仅检查扩展名,不读取文件头 -
validateMime()依赖客户端传的$_FILES['file']['type'],同样可篡改 - 若开启
moveTo()时未指定完整路径(如用$file->getInfo('name')拼路径),可能触发路径遍历(../../etc/passwd)
必须用 fileinfo 扩展做 MIME 真实类型识别
启用 PHP fileinfo 模块后,用 finfo_open(FILEINFO_MIME_TYPE) 读取文件前几百字节,才能判断真实类型。ThinkPHP 自带的 validateType() 方法底层就调用它,但默认不启用。
- 确保 php.ini 中已开启:
extension=fileinfo(Windows 下是php_fileinfo.dll) - 在验证规则中显式加入:
'type' => 'image/jpeg,image/png,image/gif',TP 会自动调用finfo校验 - 避免混用:
'ext'和'type'同时存在时,TP 只校验ext;必须删掉ext,只留type才生效 - 示例验证配置:
[ 'size' => 2097152, 'type' => 'image/jpeg,image/png', 'mime' => false ]
('mime' => false是关键,关闭对 $_FILES['type'] 的信任)
上传目录不能放在 webroot 下且需禁用脚本执行
即使文件类型校验通过,上传目录若在 Apache/Nginx 的可访问路径下,又没禁止解析,一个合法的 .htaccess 或 .user.ini 就能让 PNG 变成 PHP 执行环境。
- 存储路径应设为
ROOT_PATH . 'public/uploads/'之外的目录,例如ROOT_PATH . 'runtime/upload/' - Apache 下,在上传目录放
.htaccess:Deny from all
;Nginx 则在 server 块中加:location ^~ /uploads/ { deny all; } - 不要用
$file->move($path, $name)直接拼接用户传入的$name,必须用uniqid() . '.' . $file->getOriginalExtension()重命名 - TP 6.0+ 支持
$file->validate(['type' => ...])->move(UploadPath, true),第二个参数true表示自动重命名,推荐启用
小文件临时存储与大文件分片上传的隔离策略
TP 默认把上传文件先存到 sys_get_temp_dir(),再 moveTo。如果并发高或磁盘空间不足,临时文件可能堆积,甚至被恶意利用(如 symlink race)。大文件直传更危险——未完成的临时文件若被提前访问,可能造成内容泄露或解析失败。
- 小文件:设置
upload_tmp_dir为独立挂载点(如/data/tmp),并定期清理;TP 不提供自动清理,需自己写Runtime::getInstance()->clear('temp')或 cron 脚本 - 大文件:禁用 TP 原生上传,改用前端分片(如
webuploader或uppy)+ 后端合并逻辑,所有分片存入非 web 可达目录,合并后再校验一次 MIME - 注意
post_max_size和upload_max_filesize的协同:若前者小于后者,大文件直接被 PHP 拦截,TP 根本收不到$_FILES
真实场景里,文件类型校验和路径隔离是两道独立防线,缺一不可。哪怕 finfo 判定是 PNG,只要它落在 public/ 下又被 Nginx 配错,一样能 RCE。别省那几行配置。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











