我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 97.1% 的成绩。 了解更多

深度解析

深度解析

imgproxy ICO 加载器 RCE,已在 4.0.16 修复 (GHSA-hff9-5pqw-6w9f)

imgproxy 4.0.0 至 4.0.15 以一个会回绕的尺寸分配 ICO 数据内存,未认证的恶意图片请求可将其变成堆破坏;已在 4.0.16 修复。

摘要

  • imgproxy 4.0.0 至 4.0.15 的 ICO 图片加载器读取攻击者控制的尺寸字段,并在一次会回绕的 32 位加法中给它加 14。它随后分配远不够的内存并把图片数据写进去:一个恶意的 ICO 会让 worker 崩溃或破坏堆。厂商公告评分为 CVSS v3.1 9.8 Critical,CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H。
  • 能让代理处理恶意图片的攻击者即可触达此缺陷。imgproxy 通过 HTTP 提供图片转换,并支持 URL 签名,但签名验证默认关闭。mateofumis 和 JasonPuglisi 披露了此缺陷及两个相关 ICO 问题。
  • 修复于 2026 年 9 月 28 日随 imgproxy 4.0.16 发布,比厂商 10 月 6 日发布公告早 8 天。GitHub 公告数据库于 10 月 30 日收录了这些公告。当前版本为 4.0.20;4.0.16 及后续版本均包含修复。
  • 没有漏洞利用、被利用或受害者的报告,且截至 2026 年 10 月 31 日该公告不在 CISA 的 KEV(已知被利用漏洞)目录中。请立即升级到 4.0.16 或更高版本,并顺带检查您如何限制源 URL。

我受影响吗?

需要同时满足:

  • 您运行 imgproxy 4.0.0 至 4.0.15。4.0.16 起的版本已修复。
  • 您的实例处理攻击者控制的图片,可能通过不受限制的源 URL,也可能通过用户上传到获准来源的文件。
  • ICO 加载器在使用中,而在受影响版本中它总是如此。加载器为 ICO 格式注册自身,源按文件的魔术字节分发。

imgproxy 支持签名 URL。强制验证签名并控制应用为哪些图片签名,可以限制对加载器的访问;反向代理认证也有帮助。但这些措施既不修复加载器,也不能使恶意图片变得安全。另外,内存分配伴随公告将 3.3.0 至 4.0.15 列为受影响版本,因此运行这些旧版本的用户也应升级到 4.0.16 或更高版本。

厂商于 9 月 28 日发布修复,10 月 6 日发布公告。GitHub 公告数据库于 2026 年 10 月 30 日收录记录。因此,依赖该数据库的工具可能晚于直接监控厂商的工具获得信息。

ICO 加载器披露时间线:9 月 24 日提交修复,9 月 28 日发布 v4.0.16,10 月 6 日发布厂商公告,10 月 30 日数据库评审
图 1:修复比厂商公告早 8 天发布。来源:imgproxy 发布与安全公告,2026 年 10 月 31 日查询。

分支 受影响版本 修复版本
imgproxy 4.x 4.0.0 至 4.0.15 4.0.16 (2026-09-28)

请检查您运行的版本。二进制文件会打印自己的版本:

docker exec imgproxy imgproxy --version

镜像标签也携带版本,但 latest 之类的浮动标签需要本节的版本命令。从源码部署时,版本常量位于您检出标签的 version/version.go 中。

如何修复?

升级到 4.0.16 或更高。当前版本是 4.0.20。

  1. 在 Compose 文件中,将 imgproxy 服务的镜像设置为 darthsim/imgproxy:4.0.20,然后拉取该镜像并重新创建服务:
docker compose pull imgproxy
docker compose up -d imgproxy
  1. 二进制或源码部署请构建或下载 4.0.16 或更高版本并重启服务。

限制图片来源可以降低暴露程度,但不能代替升级。IMGPROXY_ALLOWED_SOURCES 接受允许使用的源 URL 前缀,例如:

IMGPROXY_ALLOWED_SOURCES="https://your-bucket.example/"

允许列表阻止从未经批准的来源直接获取图片。但它无法阻止上传到获准存储桶的恶意图片,也不是通用的 SSRF 修复。只允许内容可信的来源,即使已经启用限制,也应升级。

根本原因是什么?

ICO 加载器用一个可能回绕的 32 位加法,从攻击者声明的尺寸分配缓冲区,然后通过指针写入实际分配区域之外。 脆弱代码是 vips/icoload.c 中的 vips_foreign_load_ico_header,引自公告所引 4.0.15 时代提交 f4fedbb 的第 154 至 164 行:

uint32_t data_size = GUINT32_FROM_LE(largest_image_header.data_size);

VipsPel *data = (VipsPel *) VIPS_MALLOC(NULL, data_size + BMP_FILE_HEADER_LEN);
void *actual_data = data + BMP_FILE_HEADER_LEN;

if (vips_foreign_load_read_full(ico->source, actual_data, data_size) <= 0) {
  vips_error("vips_foreign_load_ico_header", "unable to read ICO image data from the source");
  return -1;
}

这段代码中的三个事实构成缺陷。data_size 是攻击者通过 ICO 目录项控制的 32 位字段。加法 data_size + BMP_FILE_HEADER_LEN 在 32 位算术中进行,头部长度为 14:对 data_size = 0xfffffff5,和回绕到 3,因此分配是三个字节。vips_foreign_load_read_full 接着收到的是原始的 data_size 而非回绕后的尺寸,并将其传给 Go 源回调,回调把源中可用的字节通过 actual_data 复制到距分配区域起点 14 字节的位置,已在三字节缓冲区之外。

公告对示例的算术:一个目录项声明 data_size = 0xfffffff5、data_offset = 22,加上 14 字节的 BMP 头部,得到 (0xfffffff5 + 14) mod 2^32 = 3。该项之后跟随的 64 字节会在读取能报告声明的数 GB 载荷不完整之前就被写入。

两个伴随缺陷位于同一函数。BMP 头部重建在任何尺寸检查之前,从微小分配之后的固定偏移读取 num_colors、bpp 和 height:越界读取变成重建头部的越界写入。而且完全没有验证时,一个 23 字节的 ICO 声明 data_size = 0xfa000068 会让 worker 尝试约 4 GB 的分配,无界分配公告称这在 4.0.15 的一个生产部署上得到确认。

如何被利用?

没有公开的完整利用。公告发布了构造值,无界分配的伴随公告附带一个附有校验和的概念验证生成脚本。步骤来自公告的源到汇路径:

  1. 攻击者托管一个恶意 ICO。其目录项声明回绕尺寸 data_size = 0xfffffff5、data_offset = 22,文件以 ICO 魔术字节 00 00 01 00 开头。
  2. 攻击者让目标的 imgproxy 处理该文件:通过源不受限的实例请求其 URL,或在部署接受时直接上传。
  3. imgproxy 从魔术字节识别出源是 ICO,并分发到 vips_icoload_source_go。
  4. vips_foreign_load_ico_header 读取攻击者的 data_size,分配回绕后的三个字节,vips_foreign_load_read_full 把原始声明尺寸传给 Go 回调。
  5. 回调写入它持有的源字节,破坏堆,进程要么中止要么带着损坏的分配器状态继续,公告将其评为拒绝服务或远程代码执行。

攻击如何流动?

一个未认证请求把恶意 ICO 通过代理送进来,在任何检查能阻止它之前,把一个回绕的尺寸字段变成堆写入。缺失的检查是分配之前对 data_size 的验证。

flowchart LR A["攻击者托管恶意 ICO
data_size 0xfffffff5"] --> B["请求到达 imgproxy,
未强制验证 URL 签名"] B --> C["源按魔术 00 00 01 00
分发到 ICO 加载器"] C --> D["缺失检查:data_size
从未被验证"] D --> E["32 位回绕:对声明的约 4 GB
分配 3 字节"] E --> F["读取回调把源字节写入
分配区域之外"] F --> G["worker 中止或堆破坏:
崩溃、潜在 RCE"]

攻击者能做什么?

攻击者让处理该图片的 imgproxy worker 崩溃,并可随意重复,这是对图片管线的拒绝服务。在崩溃之外,公告指出堆破坏“may permit remote code execution”,其标题说整数溢出“允许”它,并以 CVSS v3.1 9.8 计入对机密性、完整性和可用性的完整影响。条件就是“我受影响吗?”中的那些:受影响版本、受攻击者影响的图片来源和可达性。没有公开利用演示代码执行,因此已演示的影响是崩溃,代码执行是公告的评估。

两个伴随项增加各自的范围。BMP 头部重建的越界读写让 worker 确定性地崩溃,从 glibc 的 munmap_chunk(): invalid pointer 到 Go panic,CVSS v3.1 7.5。无界分配用 23 字节的文件耗尽内存,同样是 7.5。两者均要求攻击者能够让服务处理其图片。

截至 2026 年 10 月 31 日没有任何地方报告被利用,且该公告不在 KEV 目录中。

为什么修复有效?

提交 a821b6a4cb07"Fix segfaults during invalid ICO load"(随 4.0.16 发布)验证尺寸并只计算一次求和:

+// Maximum size of an ICO entry data block.
+// Maximum image size in ICO is 256x256 pixels,
+// which gives a maximum of 256 * 256 * 4 bytes for raw RGBA data.
+// 10 MB should be enough for any ICO entry data block,
+// even if an embedded PNG contains metadata.
+// Data blocks larger than this should be considered malicious.
+#define MAX_ICO_ENTRY_DATA_SIZE 10 * 1024 * 1024
+
 /**
  * ICO ForeignLoad VIPS class implementation (generic)
  */
@@ -153,13 +161,26 @@ vips_foreign_load_ico_header(VipsForeignLoad *load)
   uint32_t data_size = GUINT32_FROM_LE(largest_image_header.data_size);

+  // Check if the data size exceeds the maximum allowed ICO entry data size.
+  if (data_size > MAX_ICO_ENTRY_DATA_SIZE) {
+    vips_error("vips_foreign_load_ico_header", "ICO image data is too large");
+    return -1;
+  }
+
   // BMP file explicitly excludes BITMAPFILEHEADER, so we need to add it manually.
   // We reserve space for it at the beginning of the data buffer.
-  VipsPel *data = (VipsPel *) VIPS_MALLOC(NULL, data_size + BMP_FILE_HEADER_LEN);
+  uint32_t full_data_size = data_size + BMP_FILE_HEADER_LEN;
+  VipsPel *data = (VipsPel *) VIPS_MALLOC(NULL, full_data_size);
+  if (!data) {
+    vips_error("vips_foreign_load_ico_header", "unable to allocate memory for ICO image data");
+    return -1;
+  }
+
   void *actual_data = data + BMP_FILE_HEADER_LEN;

   if (vips_foreign_load_read_full(ico->source, actual_data, data_size) <= 0) {
     vips_error("vips_foreign_load_ico_header", "unable to read ICO image data from the source");
+    VIPS_FREE(data);
     return -1;
   }

10 MB 上限用一次检查同时消除回绕和无界分配。任何会让 32 位求和回绕的尺寸都远超上限,并在任何分配之前就被以错误拒绝。单一的 full_data_size 变量把一次计算的值带给之后的每次使用,因此传下去的尺寸不会再与分配的尺寸不一致。VIPS_MALLOC 之后的空值检查处理失败的分配而不是解引用它,错误路径上的 VIPS_FREE(data) 阻止了泄漏。

同一提交加入了关闭伴随越界读写的最小尺寸检查:

+    // Ensure the data buffer is large enough to contain the BMP header.
+    if (full_data_size < BMP_BITMAP_INFO_HEADER_LEN) {
+      vips_error("vips_foreign_load_ico_header", "ICO image data is too small to contain a valid BMP header");
+      VIPS_FREE(data);
+      return -1;
+    }
+
     // Read num_colors and bpp from the BITMAPINFOHEADER
     uint32_t num_colors = GUINT32_FROM_LE(*(uint32_t *) (actual_data + 32));

该修复对此加载器的 ICO 条目路径是完整的。尺寸在入口受限,分配被检查,BMP 头部重建不能再越过比头部还小的缓冲区读写。加载器的测试套件加入了公告中的恶意 ICO 测试文件 testdata/ico-bad1.ico 至 ico-bad3.ico,它们无法让修复后的加载器崩溃。

如何检查是否已被入侵?

没有发布入侵指标,因为没有报告任何受害者或攻击活动。请寻找公告描述的崩溃签名,它是尝试的可观察痕迹:

# Worker restarts and ICO-related aborts in the container log.
docker logs imgproxy --since 720h 2>&1 | grep -iE "SIGABRT|panic|ico" | head -20

让 worker 崩溃的堆破坏尝试会留下中止和重启;崩溃是线索而不是代码执行的证据。如果您怀疑该进程执行了攻击者的代码,请将容器视为已失陷,并轮换它能读取的一切,对 imgproxy 来说这通常包括其环境中的对象存储凭据。请在来源日志中检查携带来自预期来源之外 ICO 文件的请求。

时间线

  • 2026-09-24:imgproxy 提交 a821b6a4cb07"Fix segfaults during invalid ICO load"。
  • 2026-09-28:imgproxy 发布 v4.0.16,其变更日志写道"Fixed several vulnerabilities in ICO image loader"。
  • 2026-10-06:厂商将三条 ICO 公告作为已评审的 GitHub 安全公告发布:GHSA-hff9-5pqw-6w9f、GHSA-494m-4556-wf5p 和 GHSA-mmq5-xm89-9pfg。
  • 2026-10-30:GitHub 公告数据库的评审在 UTC 22:57 发布这些公告,供依赖该数据库的工具获取。

常见问题

公告为什么对远程代码执行说“may permit”?

已演示的效果是崩溃,而通往代码执行的堆破坏是公告对该缺陷所允许情况的评估。这一评估反映在它 9.8 的评分中,带有机密性、完整性和可用性的完整影响。没有公开利用把破坏带到代码执行,因此本文保留公告自己的谨慎措辞。

4.0.16 够吗,还是需要 4.0.20?

4.0.16 带有全部三个 ICO 修复,因此从它起的任何版本对这些公告都是安全的。4.0.20 只是当前版本,中间还有依赖更新。固定您测试的版本,并将其固定在 4.0.16 或更高。

我的扫描器从未标记过这个。为什么?

三条公告没有 CVE 标识符,而且直到 2026 年 10 月 30 日才进入 GitHub 公告数据库,比修复晚了一个月。只读 CVE 源或该数据库的工具在那之前什么也看不到,因此九月的一次干净扫描并不意味着实例干净。请检查版本,而不是扫描。

限制来源真的有帮助吗?

限制来源可阻止从未经批准的来源直接获取图片。但它无法保护处理获准存储桶中攻击者上传文件的服务,也不修复加载器。请升级到 4.0.16 或更高版本。

同一批的另外两条公告呢?

这批还包括自定义 BMP 加载器中的堆缓冲区溢出 GHSA-55vq-g34m-xp8f,以及允许运营商级 NAT 地址的源地址防护 GHSA-8w7m-q6hg-mfqm。全部在 4.0.16 中修复,因此一次升级覆盖整批。BMP 加载器缺陷是同样的未认证崩溃类;防护缺陷与 SSRF 相邻。

用 Ostorlab 盘点暴露的图片管线

Ostorlab 的攻击面发现会盘点您暴露在互联网上的服务。它能显示哪些部署运行着暴露的 imgproxy 端点,让您知道把哪些实例升级到 4.0.16 或更高版本、把哪些锁定到可信来源。

参考文献

  • 厂商公告 GHSA-hff9-5pqw-6w9f"Integer overflow in ICO loader causes remote denial of service and permits remote code execution via heap buffer overflow",CVSS v3.1 9.8,影响 4.0.0 至 4.0.15,修复于 4.0.16,感谢 mateofumis 和 JasonPuglisi:https://github.com/imgproxy/imgproxy/security/advisories/GHSA-hff9-5pqw-6w9f(2026 年 10 月 31 日查询)
  • 伴随公告 GHSA-494m-4556-wf5p"Out-of-bounds heap read/write in ICO loader BMP header reconstruction",CVSS v3.1 7.5:https://github.com/imgproxy/imgproxy/security/advisories/GHSA-494m-4556-wf5p(2026 年 10 月 31 日查询)
  • 伴随公告 GHSA-mmq5-xm89-9pfg"Unbounded memory allocation in ICO loader",CVSS v3.1 7.5,附经校验和验证的概念验证生成器和 4.0.15 生产确认:https://github.com/imgproxy/imgproxy/security/advisories/GHSA-mmq5-xm89-9pfg(2026 年 10 月 31 日查询)
  • 伴随公告 GHSA-55vq-g34m-xp8f,自定义 BMP 加载器的堆缓冲区溢出:https://github.com/imgproxy/imgproxy/security/advisories/GHSA-55vq-g34m-xp8f(2026 年 10 月 31 日查询)
  • 伴随公告 GHSA-8w7m-q6hg-mfqm,允许 CGNAT 地址的源地址防护:https://github.com/imgproxy/imgproxy/security/advisories/GHSA-8w7m-q6hg-mfqm(2026 年 10 月 31 日查询)
  • 修复提交 a821b6a4cb07"Fix segfaults during invalid ICO load",作于 2026 年 9 月 24 日:https://github.com/imgproxy/imgproxy/commit/a821b6a4cb07(2026 年 10 月 31 日查询)
  • imgproxy v4.0.16 发布,2026 年 9 月 28 日,变更日志行"Fixed several vulnerabilities in ICO image loader":https://github.com/imgproxy/imgproxy/releases/tag/v4.0.16(2026 年 10 月 31 日查询)
  • 公告所引提交 f4fedbb 处的脆弱 vips/icoload.c:https://github.com/imgproxy/imgproxy/blob/f4fedbb5c7f6684e7a8e0a9bb7e666773f3c40c0/vips/icoload.c(2026 年 10 月 31 日查询)
  • Docker 镜像 darthsim/imgproxy,36,809,323 次拉取:https://hub.docker.com/r/darthsim/imgproxy(2026 年 10 月 31 日查询)
  • CISA Known Exploited Vulnerabilities 目录 JSON 源,检索这些公告:https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json(2026 年 10 月 31 日查询)
  • imgproxy URL 签名: https://docs.imgproxy.net/usage/signing_url