在PHP开发的上传文件场景中,仅通过文件后缀名判断是否为图片很容易被绕过,恶意用户可以将php等可执行文件修改为jpg、png等后缀伪装成图片上传,因此需要更可靠的检测方式来判断文件是否为真实的图片类型。

方法一:使用getimagesize函数检测
getimagesize是PHP内置的用于获取图片尺寸信息的函数,当传入的文件是真实图片时,会返回包含图片宽度、高度、类型等信息的数组,若不是有效图片则返回false,这是最常用的检测方式之一。
<?php
/**
* 使用getimagesize检测文件是否为图片
* @param string $filePath 文件路径
* @return bool
*/
function checkImageByGetimagesize($filePath) {
// 检查文件是否存在
if (!file_exists($filePath)) {
return false;
}
// 调用getimagesize函数
$imageInfo = getimagesize($filePath);
// 若返回数组则说明是有效图片
if (is_array($imageInfo)) {
return true;
}
return false;
}
// 测试示例
$testFile = './test.jpg';
if (checkImageByGetimagesize($testFile)) {
echo '该文件是真实图片';
} else {
echo '该文件不是图片';
}
?>
该方法的优点是使用简单,同时还能获取到图片的基本信息,缺点是对于部分损坏的图片文件可能会误判,并且如果PHP配置中禁用了相关函数则无法使用。
方法二:读取文件头信息验证
不同格式的图片文件都有固定的文件头标识,比如jpg文件头是FF D8 FF,png文件头是89 50 4E 47,我们可以通过读取文件的前几个字节,对比这些特征标识来判断是否为图片。
<?php
/**
* 通过文件头信息检测是否为图片
* @param string $filePath 文件路径
* @return bool
*/
function checkImageByFileHeader($filePath) {
if (!file_exists($filePath)) {
return false;
}
// 以二进制只读模式打开文件
$handle = fopen($filePath, 'rb');
if (!$handle) {
return false;
}
// 读取前8个字节
$header = fread($handle, 8);
fclose($handle);
// 定义常见图片类型的文件头特征
$imageHeaders = [
// jpg/jpeg
"xFFxD8xFF",
// png
"x89x50x4Ex47",
// gif
"GIF87a",
"GIF89a",
// bmp
"BM",
// webp
"RIFF"
];
foreach ($imageHeaders as $imgHeader) {
// 检查文件头是否匹配
if (strpos($header, $imgHeader) === 0) {
return true;
}
}
return false;
}
// 测试示例
$testFile = './test.png';
if (checkImageByFileHeader($testFile)) {
echo '该文件符合图片文件头特征';
} else {
echo '该文件不符合图片文件头特征';
}
?>
这种方法的优点是直接读取文件二进制内容,不依赖PHP的图片处理扩展,适用性更广,缺点是只能检测常见的图片类型,对于小众图片格式可能覆盖不全。
方法三:结合mime类型与文件后缀验证
可以先通过finfo_file函数获取文件的真实MIME类型,再结合允许的后缀白名单进行双重验证,进一步提升安全性。
<?php
/**
* 结合MIME类型和后缀检测图片
* @param string $filePath 文件路径
* @param array $allowExt 允许的后缀白名单
* @return bool
*/
function checkImageByMimeAndExt($filePath, $allowExt = ['jpg', 'jpeg', 'png', 'gif']) {
if (!file_exists($filePath)) {
return false;
}
// 获取文件后缀
$ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
// 后缀不在白名单直接返回false
if (!in_array($ext, $allowExt)) {
return false;
}
// 获取文件的MIME类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $filePath);
finfo_close($finfo);
// 允许的图片MIME类型
$allowMime = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
// 检查MIME类型是否在允许列表中
if (in_array($mime, $allowMime)) {
return true;
}
return false;
}
// 测试示例
$testFile = './test.gif';
if (checkImageByMimeAndExt($testFile)) {
echo '文件后缀和MIME类型均符合图片要求';
} else {
echo '文件不符合图片要求';
}
?>
这种方案结合了后缀和MIME类型双重校验,能有效过滤大部分伪装文件,是生产环境中比较推荐的基础验证方案。
安全注意事项
在实际开发中,检测文件是否为图片只是安全验证的一环,还需要注意以下几点:
- 不要将上传的文件直接保存在web可访问目录,避免恶意文件被执行
- 对上传后的文件重命名,使用随机字符串作为文件名,避免原文件名带来的风险
- 可以结合getimagesize和文件头检测多种方式共同验证,提升判断的准确性
- 限制上传文件的大小,避免大文件占用服务器资源
通过以上多种方法结合使用,可以有效判断PHP中文件是否为真实的图片类型,降低文件上传功能带来的安全风险。