必须校验文件 mime 类型而非仅后缀,用 finfo_file() 获取真实类型;上传后重命名、隔离存储、禁用 web 直接访问;叠加内容扫描(如敏感字符串、getimagesize 验证)及服务器配置防护。

上传前必须校验文件 MIME 类型,不能只看后缀
很多开发者用 $_FILES['file']['name'] 提取后缀做白名单判断,这完全无效——攻击者只需把木马文件重命名为 1.jpg 就能绕过。真实类型由文件头(magic bytes)决定,PHP 的 finfo_file() 才可靠。
ThinkPHP 6 默认的 validate(['ext' => 'jpg,png,gif']) 只校验扩展名,必须配合 MIME 校验:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file->getRealPath());
finfo_close($finfo);
<p>if (!in_array($mimeType, ['image/jpeg', 'image/png', 'image/gif'])) {
throw new \Exception('非法图片类型');
}</p>
- 不要依赖
$_FILES['file']['type'],浏览器可伪造 - ThinkPHP 5.1+ 的
$file->getMime()是调用finfo的封装,可用但需确认底层是否启用 - 某些服务器禁用
finfo扩展,要提前检查extension_loaded('fileinfo')
上传后必须重命名并隔离存储,禁止直接访问原路径
即使文件内容安全,攻击者仍可能利用解析漏洞(如 Nginx 的 .php.jpg 双后缀执行、Apache 的 AddHandler 配置)触发执行。关键动作是:切断原始文件名、关闭 Web 直接访问、剥离可执行元数据。
- 用
md5_file($file->getRealPath()) . '.jpg'或 UUID 重命名,彻底丢弃用户传入的文件名 - 上传目录不要放在
public/下,ThinkPHP 推荐存到runtime/upload/等非 Web 可达路径 - 如果必须提供访问,走 PHP 文件输出(
readfile()+ 正确Content-Type),而非静态 URL - 对 PNG/JPEG 可额外用
exif_read_data()检查是否含 PHP 代码片段(少见但存在)
ThinkPHP 自带的 validate 不足以防木马,必须叠加内容扫描
ThinkPHP 的上传验证只做格式和大小限制,不检测文件体内嵌代码。常见木马会把 PHP shell 写在 JPEG APP0/APP1 段、PNG iTXt 块里,肉眼不可见,但服务器解析时可能被某些库误执行。
- 简单扫描:用
file_get_contents()读前 1KB,正则匹配<?php、eval(、system(等敏感字符串(注意二进制偏移) - 更稳妥方式:用
getimagesize()验证是否真为图像(返回 false 表示非有效图像) - 高要求场景建议集成 ClamAV 或 YARA 规则扫描,但需额外部署服务,ThinkPHP 本身不提供
- 注意 GD 库处理图片时可能触发漏洞(如旧版 libpng),确保系统组件及时更新
别忽略 .htaccess 和 Nginx 配置层的兜底防护
代码层再严密,Web 服务器配置错误也会让前面所有努力失效。比如 Apache 开启了 Options +ExecCGI,或 Nginx 错误地把 upload 目录设为 fastcgi_pass。
- Apache:上传目录下放一个
.htaccess,内容为Deny from all或SetHandler default-handler - Nginx:明确禁止执行,例如
location ~* ^/upload/.*\.(php|php5|phtml)$ { deny all; } - ThinkPHP 的
public/index.php入口已屏蔽大部分路径,但自定义路由或静态资源代理可能绕过,要逐个核对
实际中最容易被忽略的是:以为用了 validate(['ext'=>'jpg']) 就安全了,结果木马藏在 JPEG 注释段里,finfo 显示正常,getimagesize() 也通过,但某天某个新装的图像处理扩展把它当脚本执行了。防不住所有,但至少得卡住最常见那几类。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











