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

Yii2 使用 UploadedFile 进行文件上传时,仅依赖 extensions 规则或前端/扩展名校验,极易被绕过。攻击者可伪造后缀(如 shell.php.jpg)、修改 Content-Type、利用大小写/空格/双写等手法逃逸,最终上传恶意脚本。真正有效的加固必须结合服务端 MIME 检测 + 文件内容识别(magic number),而非只信 $_FILES['type'] 或文件名。
强制启用 mimeTypes 校验并禁用客户端 type 信任
Yii2 的 file 验证器支持 mimeTypes 参数,它会读取上传文件的原始二进制头(非依赖浏览器提交的 Content-Type),但默认不开启。必须显式配置,并与 extensions 联合使用:
- 在模型
rules()中明确指定合法 MIME 类型,例如:[['file'], 'file', 'extensions' => 'jpg,png,pdf', 'mimeTypes' => ['image/jpeg', 'image/png', 'application/pdf']] -
务必设置
'checkExtensionByMimeType' => true:该选项让验证器先读取文件前几百字节,解析真实 MIME,再反查是否匹配允许的扩展名。防止shell.php.jpg被当成图片放行。 - 移除所有对
$_FILES['xxx']['type']的直接引用——该字段完全由客户端控制,不可信。
补充 magic number 校验(防深度绕过)
某些老旧服务器或特殊配置下,mimeTypes 可能因 PHP fileinfo 扩展未启用而失效。此时需手动校验文件“魔数”(Magic Number):
- 在
upload()方法中,用fopen($file->tempName, 'rb')读取文件头 4–8 字节 - 比对常见类型签名,例如:
JPEG →\xFF\xD8\xFF
PNG →\x89\x50\x4E\x47\x0D\x0A\x1A\x0A
PDF →%PDF - 不匹配则抛出异常或返回验证失败,不调用
saveAs()
禁用危险扩展并统一后缀处理
即使 MIME 和魔数都通过,仍需防范 Web 服务器解析漏洞(如 Apache 的 .php5、.phtml):
-
extensions列表中绝对不要包含php, phtml, php3, phar, inc, cgi, exe, sh等任何可执行类型 - 保存文件时,强制重写后缀为白名单中的标准格式:
$safeExt = in_array($file->extension, ['jpg','jpeg','png']) ? 'jpg' : $file->extension;
再调用$file->saveAs($path . '.' . $safeExt) - 上传目录在 Web 服务器中应配置为禁止执行脚本(Nginx 加
location ~ \.(php|sh|pl|py|jsp|asp|cgi)$ { deny all; };Apache 加Options -ExecCGI和SetHandler default-handler)
额外防护:上传后隔离与权限控制
防御是分层的,最后一步是降低失陷影响:
- 上传目录不要放在
@web可直接访问路径下;若必须公开,使用符号链接或 Nginx alias 指向独立静态资源目录 - 确保
@webroot/uploads/目录权限为755(Linux/macOS),属主为 Web 进程用户(如www-data),且禁止写入权限给组和其他人 - 生产环境禁用
display_errors,避免saveAs()失败时泄露物理路径


















