PHP getimagesize安全吗

wen PHP项目 3


PHP getimagesize() 安全吗?深入剖析图片处理函数的隐藏风险与最佳实践**

PHP getimagesize安全吗


目录导读

  1. 引言:一个被低估的安全入口
  2. getimagesize() 的工作原理与常见用途
  3. 核心风险点:为什么“读取尺寸”会变成“执行代码”
  4. 真实攻击场景:图片马、二次渲染与 MIME 欺骗
  5. 安全使用 getimagesize() 的六大铁律
  6. 进阶防护:从文件头校验到图像重采样
  7. 问答环节:开发者最关心的 5 个问题
  8. 安全不是函数的事,而是架构的事

引言:一个被低估的安全入口
在 PHP 开发中,getimagesize() 几乎是处理上传图片的“标配”函数,它能快速返回图片的宽高、类型、MIME 等信息,帮助开发者验证上传文件是否为合法图片,Stack Overflow 与各大安全论坛上,getimagesize 被绕过”的讨论从未停止。这个函数本身并不是漏洞,但它常常被误用为“安全判定唯一依据”,从而打开攻击面。 本文将从底层机制出发,结合 OWASP 与 CVE 案例,回答一个核心问题:它到底安全吗?

getimagesize() 的工作原理与常见用途
该函数通过读取文件头部 12~64 字节的二进制数据(如 JPEG 的 FF D8 FF,PNG 的 89 50 4E 47),解析出图像属性,它不加载整个文件到内存,因此效率高,典型用法如下:

$info = @getimagesize($_FILES['img']['tmp_name']);
if ($info === false) { die('非图片文件'); }

开发者常以此作为“安全校验”,认为只要 getimagesize 通过,文件就是安全的。这正是问题所在——函数只校验了“文件头格式”,并未校验“文件内容是否含恶意载荷”。

核心风险点:为什么“读取尺寸”会变成“执行代码”
getimagesize() 本身不执行任何代码,也不解析图像内的附加数据,但它的返回值会被开发者用于两个高风险操作:

  • 拼接文件路径:根据返回的 MIME 类型决定扩展名(如 .jpg),然后存储文件,攻击者可构造一个“合法图片头 + PHP 恶意代码”的混合文件,通过 getimagesize 校验后,以 .php.jpg 存储,若服务器配置不当(如 .jpg 被当作 PHP 解析),恶意代码即被执行。
  • 二次引用getimagesize 返回的 bitschannels 值有时被直接用于数据库查询或日志,缺乏过滤时可能引发注入。

关键漏洞 CVE-2018-10549:PHP 5.6.36 之前的版本中,getimagesize() 在处理畸形 GIF 文件时存在堆缓冲区溢出,可导致远程拒绝服务,这说明函数自身也可能有解析缺陷

真实攻击场景:图片马、二次渲染与 MIME 欺骗

  • 图片马(Image Trojan):将 PHP 代码嵌入图片末尾。getimagesize 只检查文件头,照样返回合法尺寸,若上传目录允许脚本执行,攻击者直接访问 shell.jpg 即可运行代码。
  • 二次渲染绕过:CMS 系统(如 WordPress)会调用 GD 库重新裁剪图片,攻击者构造的图片在 GD 重绘后,恶意代码可能仍残留在 EXIF 注释区。getimagesize 在重渲染前校验通过,但重渲染后内容已变。
  • 多态文件:文件既是合法 GIF 又是合法 ZIP(Polyglot)。getimagesize 判定为图片,但解析器(如 PHP 的 phar://)可将其作为 ZIP 解压执行。

安全使用 getimagesize() 的六大铁律

  1. 不做唯一凭据:必须配合 is_uploaded_file()finfo_file()(MIME 检测)以及白名单扩展名(仅允许 .jpg.png.gif)。
  2. 重存而非信任:使用 imagecreatefromjpeg() 等函数重新生成图片,丢弃原始二进制内容,这能有效清除图片马。
  3. 禁止执行权限:确保上传目录的 php_admin_value engine off 或通过 .htaccess 禁用脚本执行。
  4. 限制文件大小getimagesize 不会读取全文件,但超大文件可能消耗内存,建议先用 filesize() 限制。
  5. 更新 PHP 版本:旧版本函数存在缓冲区溢出风险,必须保持 PHP 8.x 以上。
  6. 日志监控:对被拒绝的上传文件记录完整请求头,便于回溯攻击者。

进阶防护:从文件头校验到图像重采样
对于高安全需求场景(如社交平台头像),推荐以下流程:

// 1. 基础检查
if (!getimagesize($tmp)) { die('Invalid image'); }
// 2. 重新编码
$img = imagecreatefromstring(file_get_contents($tmp));
if (!$img) { die('Corrupted'); }
// 3. 输出为新图片(去除冗余数据)
imagejpeg($img, $dest, 90);
imagedestroy($img);

此方法彻底移除 EXIF、注释、末尾附加数据。注意:imagecreatefromstring 对畸形图片会返回 false,但需设置内存限制(memory_limit)防止 DoS。

问答环节:开发者最关心的 5 个问题

Q1:getimagesize 能防 CSRF 吗?
不能,它只验证文件格式,与请求来源无关,CSRF 防护需用 Token。

Q2:为什么我校验了 MIME 类型还是会中招?
因为 MIME 类型可从客户端伪造($_FILES['type'] 不可信),必须使用 finfo_file() 从文件内容提取 MIME。

Q3:如果只用 getimagesize,漏洞率有多高?
如果允许上传后执行,漏洞率接近 100%,攻击者只需几分钟即可构造绕过文件。

Q4:图片马在重采样后还会保留吗?
通常不会,但若恶意代码位于 EXIF 中且 GD 库忽略 EXIF,则会被清除,若位于像素数据中(如隐写),重采样后改变像素,代码可能失效。

Q5:能否用 exif_imagetype 替代?
该函数同样是只读头部,与 getimagesize 风险相同,它不能替代重采样。

安全不是函数的事,而是架构的事
getimagesize() 是一个便捷工具,而非安全边界,它的设计初衷是“获取元数据”,而“确认安全性”是开发者的责任,真正的安全在于:不信任任何用户输入、最小化文件存储权限、以及最重要的——对上传文件进行无害化重编码。 记住一句安全格言:不要问“这个函数安全吗”,要问“我的整个上传流程是否有不可绕过的防御层”。 只有将“头校验”与“内容净化”结合,才能构建真正的铜墙铁壁。

抱歉,评论功能暂时关闭!