
2023年9月12日前后安全圈几乎同时被一条消息刷屏Google Chrome 紧急推送了稳定版更新修复了编号为 CVE-2023-4863 的 WebP 堆缓冲区溢出漏洞。这个漏洞的可怕之处在于WebP 是浏览器默认支持的图片格式攻击者只要把精心构造的恶意图片放进网页用户访问页面时无需点击任何按钮就会在渲染图片的过程中触发堆缓冲区溢出可能被利用执行任意代码。从看到“WebP”和“堆缓冲区溢出”这两个词开始我就知道这次不是普通的高危通告而是一次真正值得反复咀嚼的漏洞处置案例。这件事影响的不只是 Chrome。Firefox、Edge 以及所有基于 Chromium 的浏览器全都跑不掉更麻烦的是libwebp 还被大量系统库、桌面软件、移动端图像框架和自研服务引用修复范围远远超出“升级浏览器”这一个动作。这篇文章我不会只复述公告内容而是把漏洞的技术原理、影响面判断、修复落地流程和这次处置过程中真实踩到的坑拆开讲清楚希望能帮你少走弯路。1. WebP解码器的一处“越界”为何惊动全网浏览器1.1 打开网页就能中招CVE-2023-4863的触发方式先说大家最关心的这个漏洞到底怎么触发攻击者构造一个异常的无损 WebP 图片文件把它放到一个网页里受害者用受影响的浏览器访问页面时浏览器会拉取图片并交给内置解码器处理。从图片被解析到呈现在屏幕上整个过程完全不需要用户交互不需要下载文件后再双击打开不需要点击播放按钮这就是典型的“零点击”远程攻击入口。在浏览器场景下渲染进程会调用系统中的 libwebp 库Chrome 内置了该库解码图片。解码过程中针对特殊构造的 Huffman 表代码对堆内存做出了越界写入操作。更准确地说这个漏洞在 libwebp 处理无损 WebP 格式VP8L时触发。WebP 无损压缩的核心依赖 Huffman 编码图片文件里会携带一张或者多张编码表解码器要先把这些表还原出来再用它们去解出像素数据。问题就出在还原 Huffman 表的过程中一张恶意设计的表可以让解码器在建立解码表时越界写入堆内存。有人可能会问堆缓冲区溢出不是早就有的老问题吗为什么还能引起这么大的震动关键就是“零点击”加上“远程可达”。不需要用户做任何配合只需要把网页打开恶意流量就能绕过文件下载检测、邮件网关检测直接进入渲染进程解析。虽然浏览器还有沙箱、ASLR 等防护机制但近年来大量浏览器漏洞利用链已经证明这些缓解措施并非不可饶过。只要攻击者能够拿到渲染进程内一个越界写原语再配合渲染进程内信息泄露漏洞仍有机会实现完整利用。1.2 漏洞根源不在 Chrome而在 libwebp 库这个漏洞之所以被命名为 CVE-2023-4863而不是什么“Chrome 专属漏洞”是因为它的根子在 Google 开源的 libwebp 图片处理库。Chrome 内置 libwebp 来解码 WebP 图片Firefox、Edge、以及大量 Linux 桌面环境和图像查看器也都在直接或间接地使用这个库。某个发行版如果通过动态链接方式依赖 libwebp那么只要系统里的库版本没有更新到包含修复的版本即使浏览器本身已经升级到最新依然存在被触发漏洞的路径。这次漏洞的修复版本是 libwebp 1.3.2受影响版本在此之前的全部 libwebp 1.x 系列版本都要排查。谷歌官方在 Chrome 公告中给出的修复版本是 116.0.5845.187Mac/Linux和 116.0.5845.188Windows。但如果你把视野局限在 Chrome 上那就危险了。我处理这次漏洞时的第一反应是打开内网资产清单搜索所有会解 WebP 的东西结果远超出预期桌面剪贴板工具、截图软件、邮件客户端、聊天软件内置图片预览、甚至某些门禁系统的 Web 管理后台都集成了 libwebp 的解码逻辑。同样值得关注的是Apple 在 iOS/iPadOS 16.6.1、macOS Ventura 13.5.2 等版本中修复的 CVE-2023-41064也指向 ImageIO 处理 WebP 时的同类内存破坏问题。安全社区普遍认为它与 CVE-2023-4863 指向的是同一个上游代码缺陷只是入口路径不同。这类由“一个库引发多个产品漏洞”的连锁反应恰恰印证了当你的软件防线传播链条很长时任何一个下游接入点都可能成为突破口。2. 无损WebP图片如何把堆内存“写穿”2.1 先搞懂WebP和霍夫曼表在解码中的角色要对这个漏洞有感觉得先了解 WebP 无损格式VP8L的压缩逻辑。WebP 图片分为有损压缩VP8和无损压缩VP8L两条路径CVE-2023-4863 击中在 VP8L。VP8L 的压缩流程大致是先把像素颜色转换成适合压缩的颜色空间接着做预测编码去掉相邻像素之间的冗余然后对残差做熵编码也就是用霍夫曼编码进一步压缩数据。霍夫曼编码的本质是给高频出现的符号分配更短的二进制编码给低频符号分配更长编码。这样整体数据量能明显下降但解码端必须拿到“哪个符号对应哪个码字”的对应关系才能把二进制流还原成像素数据。WebP 的无损数据流里就封装了这些编码表。解码器的工作流程是从文件里读取霍夫曼表的压缩表示重建出一张或多张完整 Huffman 表然后使用这些表逐位解码图片的像素值。问题就出在第一步读取并重建霍夫曼表时代码需要根据文件内指定的码长信息在堆上分配一个数组来记录每个码字的模式并填充这张表。这个数组的大小如果设置得不合理或者代码在填充之前没有严格检查码长上限攻击者就可以通过故意构造的超长码字序列让解码器往数组之外的内存区域写入数据。2.2 堆缓冲区溢出的具体路径libwebp 的解码实现中处理 Huffman 表的关键路径上会分配一个用于存放码字结构的表而构造出来的 WebP 文件可以在该路径上提供一些“超出预期”的码长。如果代码只是按照固定步长索引写表没有在每个写入点都做边界校验那么超过分配范围的写操作就会落到紧邻数组的堆内存块上。这个过程本质上跟我们往十个格子的书柜里硬塞第十一本书一样书柜肯定是放不下的多余的书就会挤到旁边的空间。在内存里“旁边的空间”由堆管理器管理里面可能存着另一个对象的头信息、指针字段、长度字段等等。普通情况下一次随机的越界写只会导致程序崩溃或者数据错乱但攻击者可以反复调整 WebP 文件中 Huffman 表的参数控制“多出来的那本书”的内容和写入位置把原本属于堆管理器的元数据、相邻对象的虚表指针或某个内部计数器覆盖掉。在高版本浏览器中即便是堆布局随机化实战利用仍然需要颇多技巧但这绝不意味着攻击者做不到。Chrome 官方公告里对这个问题给出的等级是 High背后就是考虑到了远程利用的可能。2.3 从越界写到代码执行还有多远从“堆缓冲区溢出”到“任意代码执行”中间还隔着不少安全机制。现代浏览器在编译时启用了栈保护、堆加固、地址随机化渲染进程内还有沙箱隔离限制了系统调用能力。但要清醒认识到浏览器漏洞利用一直是一条完整的产业链公开的 PoC 只是第一步。攻击者利用 CVE-2023-4863 的典型构想是第一步通过越界写破坏 WebAudio 或者 JavaScript 引擎对象的内部布局拿到一个信息泄露原语用这个原语绕过 ASLR第二步再借助同样的越界写改写某个函数指针或对象元数据使其跳转到堆上布局好的 shellcode第三步通过浏览器沙箱逃逸漏洞拿到系统级权限。整个过程当然不容易但以这类漏洞的重要程度一旦被商业间谍软件厂商或者攻击组织盯上剩下的只是时间和投入的问题。这就是为什么别看它只是“High”处置时务必按照“Critical ”级别去对待。3. 修复远比你想象的分散版本自查与实际影响面3.1 浏览器端修复版本对照浏览器作为最大的暴露面肯定要最先处理。下面是我整理的几个主流产品受影响版本和修复版本对照方便你按自己所在环境的实际情况核对。产品受影响版本修复版本Google Chrome 116.0.5845.187 (Mac/Linux) 116.0.5845.188 (Windows)116.0.5845.187 / 116.0.5845.188Microsoft Edge基于 Chromium 116 之前版本117.0.2045.31 等对应稳定版Firefox118 之前115 ESR 早期版本118.0.1115.3.1 ESR含相关 WebP 修复Chromium 开源浏览器未升级到包含 libwebp 1.3.2 修复的版本需要从打包方获取更新libwebp 库 1.3.21.3.2我建议第一时间做完以下检查项检查浏览器版本号方法是在地址栏输入chrome://version查看 Chrome 具体版本。确认是否开启了自动更新如果没开启立即手动更新然后重启浏览器。更新后用同样页面再次确认版本号避免终端还有残余进程导致更新不彻底。对于企业环境如果终端数量很多千万不要只依赖员工手动升级。尽量通过软件分发平台统一推送并且验证安装包实际落盘版本而不是只看分发平台上的“批准”状态。很多终端管理后台的“已批准”并不代表所有终端都真的装上了。3.2 系统库、容器与服务端组件的排查方法任务到这里才完成三分之一。规模更大的风险藏在系统库和自有代码里。绝大多数 Linux 发行版镜像、Docker 基础镜像、CI 构建环境中都包含 libwebp 的动态库或静态库。如果你有一个运行中的后端服务它加载的 libwebp 并不一定来自你的编译产物也可能来自上游基座镜像。在 Debian/Ubuntu 系环境里可以用下面的命令排查dpkg -l | grep webp apt-cache policy libwebp7 libwebp6 libwebp-dev如果版本低于 1.3.2就需要通过系统源升级apt-get update apt-get install --only-upgrade libwebp7 libwebp6 libwebp-devRedHat/CentOS/RockyLinux 系的命令则对应为rpm -qa | grep libwebp更新用dnf update libwebp。需要注意的是同一个发行版在不同大版本中打包的 libwebp 动态库文件名可能不一样比如 Debian 11 可能是 libwebp6Debian 12 可能是 libwebp7判断标准要看软件包版本号而不是单纯看库文件名。对于容器镜像必须在 CI 阶段就引入镜像扫描能力。以下是一个简单的镜像检查示例docker run --rm alpine/trivy image --severity HIGH,CRITICAL your-image:tag如果扫描结果中出现 libwebp 相关条目就需要重新构建镜像并把基础镜像我升级到包含修复的版本。这里有一个很多人忽略的细节即使你的业务镜像里没有显式安装 libwebp只要基础镜像的发行版源里默认带了它或者某个传递依赖把它拉进来镜像里就依然存在这个库。所以排查时不要只看 Dockerfile 里的 RUN 命令要直接进镜像里检查。3.3 容易被漏掉的“隐藏WebP消费者”比系统库更隐蔽的是那些不通过动态链接使用 libwebp 的程序。有些桌面软件为了兼容性和性能会静态链接 libwebp这意味着系统的 libwebp 版本更新了它也仍然使用自己内置的旧版本。这类软件在 Linux 桌面上尤其多比如一些图像编辑器、截图工具、录屏软件的内置缩略图生成器以及用 Electron 打包的跨平台应用可能内置了 Chromium 的 libwebp 代码。判断一个二进制文件是否静态链接了 libwebp可以使用字符串检索strings /path/to/binary | grep -i webp如果能搜到 libwebp 相关的符号名称比如WebPGetInfo、WebPDecode基本可以确认它内置了解码逻辑。此时你需要去软件发布页面确认是否存在针对 CVE-2023-4863 的新版本。如果软件已经停止维护没有新版本那就要把使用场景的风险等级上调并且避免用这类软件打开来自不可信来源的 WebP 图片。此外服务端语言生态里也有相当多的间接依赖比如 Python 的 Pillow 在处理部分 WebP 时会调用系统 libwebpJava 生态中的 TwelveMonkeys ImageIO 插件如果配置了 WebP 支持则会通过 JNI 调用底层 libwebpNode.js 生态里部分原生模块也有类似情况。这些场景在常规 CVE 扫描里经常被漏掉因为扫描器看的是语言包的版本而不是底层的 C 库版本。建议你在排查时把“语言层依赖”和“系统层库”分开建清单逐一确认。4. 我处理这次漏洞时踩到的真实问题4.1 只扫Chrome进程差点漏掉整片“暗面”我第一次排查完浏览器觉得松了一口气。但随后负责终端管理的同事发来一个清单里面有十几个我不认识的软件都内置了浏览器内核或图像解码组件某个内部 IM 工具在聊天窗口里渲染图片预览某个文档预览服务在缩略图生成时调用 libwebp甚至一个考勤系统的客户端也内置了 CEFChromium Embedded Framework。这些进程序列里完全看不到 Chrome 或 Firefox 的名字但它们确实会解析 WebP。这个教训很直接漏洞覆盖范围不应该以“进程名是否叫 chrome”来判断而应该以“是否包含 WebP 解析能力”来判断。如果你是安全工程师建议现在就做两件事。第一建立“浏览器内核及图片解析组件”的资产清单把项目名称、版本号、更新渠道、负责人逐项登记。第二在漏洞通告出来时优先搜索“webp”“chromium”“cef”“electron”“libwebp”这些关键词而不是只搜索产品名。4.2 SBOM和依赖扫描不背锅关键看数据准不准我有位朋友在一个中大型互联网公司负责安全响应他当时跑了一轮 SBOM 和开源组件扫描报告里显示“未发现 libwebp 漏洞”。我问他你们是怎么确认的他把报告发给我看我才发现扫描器只识别了语言层软件依赖清单里的 libwebp 条目完全没检查系统层动态库。后来他们在容器镜像里手动执行dpkg -l | grep webp结果发现基础镜像 Ubuntu 22.04 自带 libwebp 版本就是 1.2.2妥妥受影响。这件事发生后我们把安全检查流程改成了“厂商通告触发响应 语言生态扫描 系统层包扫描 二进制特征识别”四层联动的模式。任何一层都不应该单独作为“未受影响”的判定依据。特别是二进制特征识别这一层很多团队从来没做过但它恰恰是抓住静态链接问题的关键。在应急响应刚开始的半小时里与其自信地说“我们扫过没问题”不如多花几分钟把动态库和静态链接二进制都过一遍。4.3 不要自己做补丁等官方版本更新有些团队为了赶时间可能会试图下载 libwebp 源码从上游补丁改动中 cherry-pick 某个 commit自己编译一个补丁库塞进生产环境。我强烈不建议这么做。原因有三层第一你自己编译的版本不一定包含官方完整的边界校验逻辑上游补丁往往还包含内存分配策略调整和测试用例只挑其中一个 commit 很可能把修复修成半吊子第二你编译出的库没有经过发行版仓库的质量验证兼容性不确定生产环境出现奇怪的解码异常时你很难把问题定位到库本身第三后续官方发布更高版本时你自己编译的行为会让自动化扫描工具更难以判断真实版本。正确操作是动态链接场景用发行版源升级静态链接场景找软件厂商的新版本确实没有新版本可用的场景就先按临时缓解方案限制入口同时观察上游有没有持续维护的计划。绝不建议用“自己改源码”这种方式作为生产环境的长期策略。5. 升级前的临时缓解方案也要分清楚优先级5.1 浏览器侧能做的几个调整在补丁全面铺开之前至少可以通过浏览器策略临时降低一部分暴露面。企业环境里锐减风险最有效的是强制开启自动更新并禁用“关闭浏览器时清除自动更新数据”这类容易让人误解的选项。对于 IT 管理员来说可以为此写一个简单的策略下发脚本# 以 Chrome 组策略为例启用后台更新 defaults write com.google.Keystone.Agent checkInterval 3600不过在个人电脑上这些策略脚本并不通用更简单的做法是在浏览器设置里确认“自动更新”开关打开。如果实在担心不可信来源的图片安全问题短期内可以限制浏览不受信任的网站同时尽量使用最新的正式版浏览器。不要轻易在地址栏里禁用 WebP 或者关闭图片加载因为现代网页大量依赖图片这种方式的副作用太大会严重影响业务也难以长期坚持。5.2 网关/CDN侧的补救思路对于网络侧可以尝试在 HTTP 层面对 WebP 图片响应做更严格的策略比如对Content-Type: image/webp的响应触发额外检测或者在互联网出口统一拦截来自不可信域的 image/webp 资源。但说实话这个方案在绕过面上实在太大图片可以伪装成其他 Content-Type可以放在数据 URI 里也可以通过 XHR/fetch 拉取后再交给解码器。网关层拦截顶多当作纵深防御的一环千万不要把它当成可以替代终端修复的主方案。真正有价值的网络侧工作是监控和检测。如果企业 IDS/NDR 设备能识别 WebP 解码异常导致的进程崩溃信号或者能够采集终端上的浏览器崩溃转储那么至少可以在出现真实攻击时提前发现蜘蛛马迹。我在处置过程中就接触过一个案例某台终端 Chrome 渲染进程频繁崩溃日志里出现了 libwebp 相关堆访问异常排查后确认用户访问了一个带恶意图片的钓鱼页面。崩溃本身不一定代表漏洞利用成功但大量崩溃确实是值得警惕的信号。5.3 缓解措施的天然边界它替代不了补丁最后说一个最容易被忽略的事实所有“临时缓解方案”都有过期时间也都无法覆盖所有场景。企业里常常出现这样的情况漏洞通告出来负责人说“我们已经启用网关拦截规则”结果三个月之后扫描器仍然在几百台终端上扫出旧版本 libwebp。原因很简单缓解方案没有真正改变终端状态只要漏洞存在风险就在那里。因此我给这次事件定的处置优先级是先升级浏览器及所有直接暴露给互联网用户的组件再升级系统库和容器基础镜像最后排查静态链接的存量软件并逐个联系厂商。与此同时建立长期机制把 libwebp 这类被大量复用的开源组件列入重点跟踪清单每次上游发布新版本都要对照自家资产清单跑一遍版本匹配。把这次处置积累下来的经验沉淀成脚本和流程下次再有类似的图像库漏洞通告就不会手忙脚乱了。