关键在于不信任$_FILES'file',必须用exif_imagetype()等服务端真实解析文件头校验类型,并配合白名单、重命名、上传目录禁解析等多重防御。

PHP 7.4 防御 Content-Type 伪造(即 MIME 类型欺骗),关键在于不信任客户端传来的 $_FILES['file']['type']。这个值完全由浏览器生成,可被 Burp Suite、curl 或任意 HTTP 客户端轻易篡改,服务端若仅靠它做校验,形同虚设。
核心原则:用服务端真实解析代替客户端声明
不能只检查 $_FILES['file']['type'],必须结合文件内容本身做可信判断。常见可靠手段有:
-
读取文件头(Magic Number):用
fopen + fread读前几个字节,比对 JPEG(FFD8)、PNG(89504E47)、GIF(47494638)等标准标识 -
调用扩展库解析:启用
php-gd后用getimagesize()或exif_imagetype()—— 这两个函数会实际解析图像结构,返回整型类型常量(如IMAGETYPE_JPEG),伪造的 Content-Type 无法影响结果 -
禁用危险扩展名写入可执行目录:即使文件是图片,也要确保上传后存放路径不被 Web 服务器解析为 PHP(例如放在
/uploads/并配置 Nginx 不解析该目录下的.php)
代码示例:安全校验图像上传
以下是一个 PHP 7.4 下推荐的校验片段:
$tmp = $_FILES['file']['tmp_name'];
$allowed_types = [IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_GIF];
// 真实解析文件头,非依赖 $_FILES['type']
$image_type = exif_imagetype($tmp);
if (!$image_type || !in_array($image_type, $allowed_types)) {
die('非法图像格式');
}
// 可选:再加一层文件头字节校验增强鲁棒性
$bytes = file_get_contents($tmp, false, null, 0, 4);
if ($image_type === IMAGETYPE_JPEG && substr($bytes, 0, 2) !== "\xFF\xD8") {
die('JPEG 文件头异常');
}
配套加固措施不能少
单靠 MIME 校验不够,需组合防御:
- 在
php.ini中关闭远程包含:allow_url_include = Off - 限制上传目录权限:
chmod 755目录、chmod 644文件,且禁止该目录执行脚本 - 重命名上传文件:
uniqid() . '.' . strtolower(pathinfo($original_name, PATHINFO_EXTENSION)),避免原名携带恶意信息 - 设置
upload_max_filesize和post_max_size,防大文件耗尽资源
为什么黑名单后缀+Content-Type 检查是危险组合
很多老代码写成这样:
if ($_FILES['file']['type'] === 'image/jpeg' && pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION) === 'jpg') { ... }
这种写法存在双重风险:一是 $_FILES['type'] 可伪造;二是扩展名可通过 shell.php.jpg 绕过(尤其当服务器配置不当,仍会解析 PHP)。PHP 7.4 下更应坚持白名单 + 内容解析 + 目录隔离三者并用。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











