必须启用fileinfo扩展并用finfo_file()校验真实MIME类型,结合mimeTypes白名单、图像重绘剥离元数据、上传目录禁用脚本执行三重防护,缺一不可。

在Yii2中实现文件上传时,仅靠扩展名或$_FILES['x']['type']判断MIME类型会导致严重安全漏洞,必须基于文件二进制内容校验真实类型并叠加多重防护。
启用fileinfo扩展并验证基础环境
检查php.ini中是否已启用extension=fileinfo——【若未启用,finfo_open()将直接报错,后续所有MIME校验逻辑均失效】。可通过phpinfo()页面搜索“fileinfo”确认,或执行php -m | grep fileinfo验证。没有该扩展,所有基于魔术字节的校验都不可行。
在模型规则中声明MIME类型白名单
在UploadForm类的rules()方法中,显式指定mimeTypes选项:
return [
[['imageFile'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg, gif, webp', 'mimeTypes' => ['image/png', 'image/jpeg', 'image/gif', 'image/webp']],
];
注意:mimeTypes必须写全小写、带斜杠的标准格式,不能写成image/jpg或image\jpeg;否则Yii2内部比对失败,该规则形同虚设。
重写upload()方法,插入真实MIME校验
第一步:调用父类validate()完成扩展名与基础规则检查。
第二步:获取临时文件路径$_FILES['imageFile']['tmp_name'],用finfo_file()读取真实MIME类型。
第三步:严格比对白名单数组,不接受通配符(如image/*)或模糊匹配——【必须完全相等,否则攻击者可上传伪装成image/webp实为PHP的文件绕过】。
第四步:校验通过后才调用saveAs()保存文件。
示例关键代码段:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$realType = finfo_file($finfo, $this->imageFile->tempName);
finfo_close($finfo);
if (!in_array($realType, ['image/png', 'image/jpeg', 'image/gif', 'image/webp'])) {
return false;
}
增加图像重绘环节剥离元数据
方法一:使用GD库重绘PNG/JPEG
根据$realType选择imagecreatefrompng()或imagecreatefromjpeg()加载图像,再用imagepng()/imagejpeg()另存为新文件。这一步能清除EXIF、ICC Profile、隐藏PHP代码等所有非像素数据。
方法二:使用Imagine扩展(推荐)
安装composer require yiisoft/yii2-imagine,然后在upload()中调用:
$imagine = new \Imagine\Gd\Imagine();
$image = $imagine->open($this->imageFile->tempName);
$image->save('uploads/clean_' . $this->imageFile->baseName . '.png');
这比手动GD更健壮,自动适配CMYK、透明通道等边缘情况。
禁用上传目录的脚本执行权限
Nginx配置中添加location块限制.php后缀访问:
location ^~ /uploads/ {
location ~ \.php$ { deny all; }
}
Apache环境下,在uploads目录放置.htaccess文件:
php_flag engine off
这两项配置必须落实,否则即使MIME校验通过,攻击者仍可能上传含恶意代码的图片并在解析时触发RCE。


















