不能只用pathinfo()判断图片后缀,因为其仅解析文件名字符串,无法识别内容真实类型;攻击者可上传shell.php.jpg等伪装文件,pathinfo返回jpg通过白名单校验,但finfo_file()能检测出真实MIME为text/x-php,必须结合文件头校验与扩展名双重验证。

为什么不能只用 pathinfo() 判断图片后缀
很多人直接用 pathinfo($filename, PATHINFO_EXTENSION) 取扩展名再比对白名单,这在上传场景下完全不可信——攻击者可轻易伪造 .jpg 后缀但实际内容是 PHP 代码或恶意脚本。浏览器、HTTP 请求、甚至前端 JS 都可被绕过,后端必须以文件**真实内容**为准。
正确做法是读取文件头(magic bytes),结合 MIME 类型解析和扩展名双重校验。PHP 的 finfo_open() 是最可靠的选择,它基于 libmagic 数据库识别二进制特征。
- 仅依赖扩展名:
$_FILES['img']['name'] === 'shell.php.jpg'会被放过 -
finfo_file()能识别出该文件真实 MIME 是text/x-php - 注意:
finfo_open()默认不启用FILEINFO_MIME_TYPE,需显式传参 - Windows 下若未加载
fileinfo扩展会报致命错误,应提前检查extension_loaded('fileinfo')
如何用 finfo_open() 实现严格白名单校验
核心逻辑是:打开文件句柄 → 用 finfo_file() 获取真实 MIME → 映射到预设白名单数组 → 同时验证原始扩展名是否匹配该 MIME 的常规后缀(防 MIME 欺骗)。
class ImageWhitelistValidator
{
private array $allowedMimes = [
'image/jpeg' => ['jpg', 'jpeg'],
'image/png' => ['png'],
'image/gif' => ['gif'],
'image/webp' => ['webp'],
'image/avif' => ['avif'],
];
public function isValidImage(string $filePath): bool
{
if (!is_readable($filePath)) {
return false;
}
$finfo = finfo_open(FILEINFO_MIME_TYPE);
if ($finfo === false) {
return false;
}
$mime = finfo_file($finfo, $filePath);
finfo_close($finfo);
if (!$mime || !isset($this->allowedMimes[$mime])) {
return false;
}
$ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
return in_array($ext, $this->allowedMimes[$mime], true);
}
}
- 不要用
$_FILES['img']['tmp_name']直接传入 —— 必须确保文件已完整写入磁盘且可读 - 避免调用
getimagesize()替代:它可能触发 GD 解码,对畸形图片有崩溃或内存溢出风险 - WebP 和 AVIF 需 PHP ≥ 7.4 且 libmagic 数据库较新,旧系统可能识别为
application/octet-stream
上传时如何配合 move_uploaded_file() 安全校验流程
白名单类不能脱离上传上下文独立使用。典型陷阱是:先移动文件,再校验;一旦移动失败或校验失败,残留文件可能堆积或被误访问。
立即学习“PHP免费学习笔记(深入)”;
安全顺序必须是:临时文件 → 内容校验通过 → 重命名并移动 → 清理临时文件。
- 校验前确保
$_FILES['img']['error'] === UPLOAD_ERR_OK,否则跳过后续 - 用
sys_get_temp_dir()+uniqid()构造临时目标路径,避免竞态条件 - 校验通过后,用
move_uploaded_file($_FILES['img']['tmp_name'], $finalPath),而非copy()或rename() - 若校验失败,务必显式
unlink($_FILES['img']['tmp_name']),防止临时文件滞留 - 最终保存路径禁止拼接用户输入的文件名,应生成服务端唯一名(如
sha256(file_get_contents())) . '.jpg')
常见绕过手法与对应防御点
攻击者常组合利用多个漏洞,单点防护容易失效。比如:上传 shell.jpg,但文件头伪造为 JPEG,结尾嵌入 PHP 标签,再通过 Apache 的多后缀解析(shell.jpg.php)触发执行。
- 禁用多后缀解析:Apache 配置中移除
AddHandler php-script .php对非.php后缀的映射 - Nginx 需限制
location ~ \.php$,拒绝匹配/upload/xxx.jpg.php - 上传目录设置
deny all(Nginx)或Options -ExecCGI(Apache),禁止脚本执行 - 不要信任
$_FILES['img']['type']—— 它来自客户端 HTTP 头,完全可控 - 对 WebP/AVIF 等较新格式,建议在生产环境开启
fileinfo日志,记录识别失败的文件样本用于规则迭代
白名单本身不是银弹,它只是内容可信链的第一环。真正难的是把校验嵌入到整个上传生命周期里,而不是写一个“看起来很全”的类就完事。



















