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 日收录记录。因此,依赖该数据库的工具可能晚于直接监控厂商的工具获得信息。
| 分支 | 受影响版本 | 修复版本 |
|---|---|---|
| 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。
- 在 Compose 文件中,将 imgproxy 服务的镜像设置为
darthsim/imgproxy:4.0.20,然后拉取该镜像并重新创建服务:
docker compose pull imgproxy
docker compose up -d imgproxy
- 二进制或源码部署请构建或下载 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 的一个生产部署上得到确认。
如何被利用?
没有公开的完整利用。公告发布了构造值,无界分配的伴随公告附带一个附有校验和的概念验证生成脚本。步骤来自公告的源到汇路径:
- 攻击者托管一个恶意 ICO。其目录项声明回绕尺寸
data_size = 0xfffffff5、data_offset = 22,文件以 ICO 魔术字节00 00 01 00开头。 - 攻击者让目标的 imgproxy 处理该文件:通过源不受限的实例请求其 URL,或在部署接受时直接上传。
- imgproxy 从魔术字节识别出源是 ICO,并分发到
vips_icoload_source_go。 vips_foreign_load_ico_header读取攻击者的data_size,分配回绕后的三个字节,vips_foreign_load_read_full把原始声明尺寸传给 Go 回调。- 回调写入它持有的源字节,破坏堆,进程要么中止要么带着损坏的分配器状态继续,公告将其评为拒绝服务或远程代码执行。
攻击如何流动?
一个未认证请求把恶意 ICO 通过代理送进来,在任何检查能阻止它之前,把一个回绕的尺寸字段变成堆写入。缺失的检查是分配之前对 data_size 的验证。
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