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

深度解析

深度解析

libexpat 十亿笑话(billion laughs)防护绕过,2.10.0 修复(CVE-2026-115506)

libexpat 2.9.0 及更早版本对某些十亿笑话变体检测过晚;2.10.0 修复了该问题及另外三个解析器缺陷。

TL;DR

  • 2026 年 11 月 2 日发布的 libexpat 2.10.0 修复了四个安全缺陷。最大的是 CVE-2026-115506,一个现有防护检测过晚的十亿笑话(billion laughs)变体。另外三个是 storeAtts 中的有符号整数溢出(CVE-2026-118370)、通过 XML_ParserReset 的拒绝服务(CVE-2026-113351)以及空子解析器输入上的 NULL 解引用(CVE-2026-117610)。
  • 直至前一个版本 2.9.0 的所有版本都受影响。CVE 记录给 CVE-2026-115506 评分 8.7 High,CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N,其余三个各评分 8.2 High。
  • 尚未发现利用行为,截至 2026 年 11 月 2 日四个 CVE 都不在 CISA 的 KEV 目录中。Hanno Böck 的公开载荷模糊测试器生成了暴露检测缺口的那批变体。
  • 升级到 2.10.0,或等待发行版对四个修复的向后移植。捆绑 Expat 副本的项目应按维护者的要求更新内置副本。

我受影响吗?

以下条件必须全部成立:

  • 您运行 libexpat 2.9.0 或更早版本,或捆绑了它的软件。Python 的 xml.parsers.expat 模块链接了 Expat,CPython 仓库自身也在 Modules/expat 下带有一份副本。
  • 您的软件解析攻击者可控的 XML 文档,或使用解析 XML 的库。
  • 各缺陷的前提不同:CVE-2026-115506 只需要一个中等大小的文档,而 CVE-2026-118370 需要一个开头标记携带超过 2^30 个属性的多 GB 文档。

维护者称 Expat “packaged everywhere”(到处都有打包),并将它列为最广泛使用的两个 C 语言自由 XML 解析器之一。发行版把它作为运行时库收录,Python、Perl 和 Apache 生态都建立在它之上。Debian 的安全跟踪器已经把 expat 包标记为在所有受支持的套件中存在漏洞。

libexpat 2.10.0 安全发布时间线:第一个 CVE 于 10 月 20 日发布,修复于 10 月 23 日至 29 日合并,发布于 2026 年 11 月 2 日。
图 1:从第一个 CVE 发布到修复全部四个问题的版本共经历 13 天。来源:CVE 记录、libexpat 拉取请求与 2.10.0 发布,2026 年 11 月 2 日核对。

分支 受影响版本 修复版本
libexpat 稳定版 2.7.2 至 2.9.0(CVE-2026-113351);1.95.2 至 2.9.0(其余三个) 2.10.0(2026-11-02)

检查您链接的版本。Debian 和 Ubuntu 以 libexpat1 提供它;Python 解释器会报告其链接副本的版本:

dpkg -l libexpat1
python3 -c "import xml.parsers.expat as e; print(e.EXPAT_VERSION)"

输出形如 expat_2.9.0 即为受影响版本。截至 2026 年 11 月 2 日,Debian 安全跟踪器将 bookworm、trixie、forky 和 sid 中的 expat 包标记为存在漏洞且未修复。

如何修复?

升级到 libexpat 2.10.0。

  1. 如果您从发行版安装,请等待其安全更新并在发布时应用;维护者的公告说明该版本使全部四个修复可供向后移植。
  2. 如果您从源码构建或捆绑副本,请把内置目录更新到 R_2_10_0 标签并重新构建所有使用方。维护者的公告请打包者和捆绑方更新到 2.10.0。
  3. 如果您维护向后移植,拉取请求 #1439 是维护者标记为移植十亿笑话修复时必不可少的后续。

升级仍是主要修复措施。Expat 自 2.6.0 起支持通过 XML_GE 在构建时禁用通用实体。在运行时,XML_SetDefaultHandler 会抑制文本中内部定义的通用实体展开,但其行为与可用性取决于嵌入应用或语言绑定。这些功能并非已部署应用的通用运行时开关。不需要 DTD 的应用可在解析前拒绝带 DTD 的文档,但也会拒绝合法文档。请参阅 Expat API 文档。

根本原因是什么?

四个缺陷都位于解析器核心 expat/lib/xmlparse.c。本节引用 2.9.0 标签处的脆弱代码。

CVE-2026-115506:放大检查在展开工作之后运行。 Expat 在解析时逐令牌统计直接字节和间接字节,并在比值超过阈值时拒绝。检查函数为:

static XML_Bool
accountingDiffTolerated(XML_Parser originParser, int tok, const char *before,
                        const char *after, int source_line,
                        enum XML_Account account) {
  ...
  *additionTarget += bytesMore;
  const XmlBigCount countBytesOutput
      = rootParser->m_accounting.countBytesDirect
        + rootParser->m_accounting.countBytesIndirect;
  const float amplificationFactor
      = accountingGetCurrentAmplification(rootParser);
  const XML_Bool tolerated
      = (countBytesOutput < rootParser->m_accounting.activationThresholdBytes)
        || (amplificationFactor
            <= rootParser->m_accounting.maximumAmplificationFactor);
  ...
  return tolerated;
}

该函数从令牌处理路径调用,也就是说当计数器更新时,Expat 已经完成了该令牌实体内容的展开。Hanno Böck 的模糊测试器 lololol 找到了这样的参数组合:解析器执行数十秒的展开后,累计比值才越过阈值。修复(拉取请求 #1433)给每个实体添加 expandedSize 字段,在展开开始前预先计算实体会展开到多远。拒绝因此提前发生,拉取请求 #1439 又把扫描扩展到实体值内的 CDATA 节、注释和处理指令。

CVE-2026-118370:storeAtts 中的两个有符号计数器溢出。 在 2.9.0 中该函数声明:

static enum XML_Error
storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr,
          TAG_NAME *tagNamePtr, BINDING **bindingsPtr,
          enum XML_Account account) {
  DTD *const dtd = parser->m_dtd; /* save one level of indirection */
  int attIndex = 0;
  XML_Char *uri;
  int nPrefixes = 0;

两个 int 计数器在 2^31 个属性处回绕。携带超过 2^30 个属性的开头标记使算术越界,破坏堆。修复(拉取请求 #1450)把两者改为 size_t,并在值存入 int 类型的解析器字段处加了显式的 INT_MAX 检查。

CVE-2026-113351:XML_ParserReset 从不抵消分配跟踪器。 Expat 2.7.2 引入了允许分配与文档大小成比例的堆跟踪器。重置解析器以复用会保留池化内存,但跟踪器的 bytesAllocated 总数保留了旧文档的分配。随后在同一解析器上解析普通文档可能被 XML_ERROR_NO_MEMORY 拒绝。修复(拉取请求 #1431)记录 bytesAllocatedWhenLastReset 并将其扣除,让每个文档从零开始。

CVE-2026-117610:switch 缺少一个 case。 2.9.0 的 externalEntityInitProcessor2 处理 XML_TOK_BOM、XML_TOK_PARTIAL 等,但没有 XML_TOK_NONE 的 case。外部实体子解析器上的空的非最终首次解析会直接落到 externalEntityInitProcessor3,随后一次解析导致崩溃。拉取请求 #1444 添加了该 case 并干净地返回 XML_ERROR_NONE。

如何被利用?

四个问题都没有完整的公开利用。攻击输入来自 CVE 记录和维护者公告。

  1. 攻击者发送一个包含大型 DTD 和嵌套实体展开的中等大小 XML 文档。公开的模糊测试器将其校准到尽可能长时间停留在检测阈值之下,解析在 accountingDiffTolerated 拒绝之前消耗数十秒的 CPU(CVE-2026-115506)。
  2. 攻击者提交一个多 GB 的文档,其唯一的开头标记携带超过 2^30 个属性。storeAtts 的 int 计数器回绕,属性复制循环越过缓冲区写入(CVE-2026-118370)。
  3. 攻击者在应用通过 XML_ParserReset 复用的解析器上先解析一个大文档,随后的普通文档就以 XML_ERROR_NO_MEMORY 失败,直到重新创建解析器(CVE-2026-113351)。
  4. 攻击者控制外部实体输入流的分块,并以一个空的非最终块开头,使下一次解析崩溃进程(CVE-2026-117610)。

生成第 1 步攻击文档的模糊测试器是公开的:github.com/hannob/lololol。

攻击如何流动?

一句话概括:一个带有深层实体树的恶意文档比放大检查察觉的速度更快地展开,解析器在拒绝前烧掉大量 CPU,或者属性计数器回绕导致越界写入。

flowchart LR A["攻击者控制的
XML 文档"] --> B["实体展开增加
间接字节"] B --> C["放大检查只在
每个令牌之后运行"] C --> D["展开工作发生在
拒绝之前"] D --> E["每次解析消耗
数十秒 CPU"]

攻击者能做什么?

已记录的影响是解析进程的拒绝服务,以及对 CVE-2026-118370 而言的堆越界写入:

  • 攻击者无需特权,也无需向解析它的系统投递文档之外的交互。四个 CVSS v4.0 向量都记录了网络攻击向量。
  • CVE-2026-115506 和 CVE-2026-113351 是拒绝服务缺陷。CVE 记录如此描述它们,维护者公告没有评估任何代码执行影响。
  • CVE-2026-118370 是内存破坏,但前提苛刻:一个多 GB 的文档,单个开头标记要有超过 2^30 个属性。CVE 记录止步于“应用程序崩溃或堆内存损坏”,没有来源演示代码执行。
  • CVE-2026-117610 在攻击者控制流分块时使进程崩溃,CVE 记录注明了这一前提。

为什么修复有效?

四个拉取请求关闭这四个机制。

对 CVE-2026-115506,拉取请求 #1433 在展开开始前预先计算每个实体的 expandedSize,放大检查改查投影尺寸而不是等待字节计数器:

  /* Indirect accounting bytes charged when this entity is expanded: 1 per text
     character, plus each reference's "&name;"/"%name;" token length and its own
     expansion. ... */
  XmlBigCount expandedSize;

对 CVE-2026-118370,拉取请求 #1450 改动两个计数器并对存储加以限制:

-  int attIndex = 0;
+  size_t attIndex = 0;
   XML_Char *uri;
-  int nPrefixes = 0;
+  size_t nPrefixes = 0;

对 CVE-2026-113351,拉取请求 #1431 添加重置水位:

+  size_t bytesAllocatedWhenLastReset;

XML_ParserReset 现在记录重置时刻的分配总数,下一个文档的容忍检查会扣除前一个文档留下的部分。

对 CVE-2026-117610,拉取请求 #1444 添加缺失的 case:

+  case XML_TOK_NONE:
+    if (! parser->m_parsingStatus.finalBuffer) {
+      *endPtr = start;
+    }
+    return XML_ERROR_NONE;

维护者指出,这一版本总共关闭 12 个安全问题,而同期有 20 个新发现,发布时仍有 13 个已知未修复的安全问题。四个修复对它们针对的机制是完整的;是否还有更多十亿笑话变体,正是模糊测试器继续探索的问题。

如何检查是否被入侵?

四个缺陷在设计上都不外泄数据,检查聚焦可用性异常。

  1. 审查处理不可信输入的 XML 解析器日志:XML_ERROR_NO_MEMORY 可作为调查 CVE-2026-113351 的线索;解析时长时间消耗 CPU 以及 XML_ERROR_AMPLIFICATION_LIMIT_BREACH 错误可作为调查 CVE-2026-115506 的线索。这些现象本身不能证明存在利用。
  2. 查找向解析 XML 的服务投递异常大 XML 载荷的请求,包括任何多 GB 的提交(CVE-2026-118370)。
  3. 对 CVE-2026-117610,检查外部实体子解析器路径的崩溃转储。

一个被 Expat 拒绝的十亿笑话文档除拒绝错误外不留痕迹,而一次拒绝并不意味着服务被拖垮。

时间线

  • 2026-10-20:针对 XML_ParserReset 拒绝服务的 CVE-2026-113351 发布,由 Pu Cheng 报告。
  • 2026-10-22:在 Hanno Böck 的模糊测试器发现变体之后,针对十亿笑话检测缺口的 CVE-2026-115506 发布。
  • 2026-10-23:XML_ParserReset 修复(拉取请求 #1431)合并。
  • 2026-10-26:十亿笑话后续修复(拉取请求 #1439)合并。
  • 2026-10-28:CVE-2026-117610 和 CVE-2026-118370 发布。
  • 2026-10-29:其余修复(拉取请求 #1444 和 #1450)合并。
  • 2026-11-02:包含全部四个修复的 libexpat 2.10.0 发布。

常见问题

四个问题中应优先处理哪个?

CVE-2026-115506。它的输入是任何 XML 端点都会接受的中等大小文档,代价是每次解析数十秒的 CPU。CVE-2026-118370 是内存破坏,但多 GB 的前提使它难以投递得多。

这影响 Python 应用吗?

影响任何链接了受影响 Expat 的进程,包括通过 xml.parsers.expat 使用它的 Python。解释器用 EXPAT_VERSION 报告链接版本。在链接系统包的 Python 构建上,升级系统的 libexpat1 就会更新解释器的解析器。

其中有没有被实际利用?

四个问题都没有利用报告,截至 2026 年 11 月 2 日都不在 CISA 的 KEV 目录中。公开的模糊测试器演示了攻击,并非实际使用。

为什么一个缺陷有 2.7.2 的下限而其他没有?

CVE-2026-113351 破坏的分配跟踪器是 2.7.2 引入的,这个缺陷不可能更早存在。其余三个早于其防护,或位于 2.0 之前就存在的路径中,因此记录只说“直至 2.9.0”。

能直接禁用实体展开吗?

构建时与运行时控制有所区别:XML_GE 在构建时控制通用实体支持,而 XML_SetDefaultHandler 改变文本中的内部通用实体展开。请检查应用或语言绑定提供哪些功能并测试兼容性;升级到 2.10.0 仍是主要修复措施。

谁发现并修复了这些问题?

Hanno Böck 用他的模糊测试器发现了十亿笑话变体,Pu Cheng 报告了重置缺陷。展开前预计算这一修复关键思路由 Shani Yosef 带来,发布公告和 CVE 致谢对此有记录。

用 Ostorlab 找出 Expat 在您环境中的运行位置

Ostorlab 的依赖扫描为您的服务和容器镜像安装的包建立完整清单,从而找到每个携带受影响 libexpat 的部署。运行 Ostorlab 扫描,在所有关键位置确认 2.10.0 或向后移植的更新。

参考文献

  • Expat 维护者发布公告“Expat 2.10.0 released, fixes four vulnerabilities”,2026 年 11 月 2 日发布,包含攻击解释、致谢以及拉取请求 #1439 的移植注记:https://blog.hartwork.org/posts/expat-2-10-0-released/(2026 年 11 月 2 日核对)
  • oss-security 发布公告“libexpat 2.10.0 fixes two vulnerabilities”,2026 年 11 月 2 日发布,列出四个 CVE、更新日志条目和修复拉取请求:https://www.openwall.com/lists/oss-security/2026/11/02/6(2026 年 11 月 2 日核对)
  • GitHub 上的 Expat 2.10.0 发布页,2026 年 11 月 2 日发布:https://github.com/libexpat/libexpat/releases/tag/R_2_10_0(2026 年 11 月 2 日核对)
  • CVE-2026-115506 记录,2026 年 10 月 22 日发布,CVSS v4.0 8.7 High、CVSS v3.1 7.5 High:https://www.cve.org/CVERecord?id=CVE-2026-115506(2026 年 11 月 2 日核对)
  • CVE-2026-113351 记录,2026 年 10 月 20 日发布,CVSS v4.0 8.2 High、CVSS v3.1 5.9 Medium:https://www.cve.org/CVERecord?id=CVE-2026-113351(2026 年 11 月 2 日核对)
  • CVE-2026-117610 记录,2026 年 10 月 28 日发布,CVSS v4.0 8.2 High、CVSS v3.1 5.9 Medium:https://www.cve.org/CVERecord?id=CVE-2026-117610(2026 年 11 月 2 日核对)
  • CVE-2026-118370 记录,2026 年 10 月 28 日发布,CVSS v4.0 8.2 High、CVSS v3.1 5.9 Medium:https://www.cve.org/CVERecord?id=CVE-2026-118370(2026 年 11 月 2 日核对)
  • CVE-2026-113351 的修复拉取请求 #1431,2026 年 10 月 23 日合并:https://github.com/libexpat/libexpat/pull/1431(2026 年 11 月 2 日核对)
  • CVE-2026-115506 的修复拉取请求 #1433(预计算展开大小)及其处理 CDATA、注释和处理指令的后续 #1439:https://github.com/libexpat/libexpat/pull/1433(2026 年 11 月 2 日核对)
  • CVE-2026-117610 的修复拉取请求 #1444,2026 年 10 月 29 日合并:https://github.com/libexpat/libexpat/pull/1444(2026 年 11 月 2 日核对)
  • CVE-2026-118370 的修复拉取请求 #1450,2026 年 10 月 29 日合并:https://github.com/libexpat/libexpat/pull/1450(2026 年 11 月 2 日核对)
  • Hanno Böck 针对十亿笑话变体的载荷模糊测试器,从 Expat 更新日志链接:https://github.com/hannob/lololol(2026 年 11 月 2 日核对)
  • Expat 项目主页,描述 Expat 到处有打包并列出用户、绑定和封装:https://libexpat.github.io/(2026 年 11 月 2 日核对)
  • R_2_9_0 标签处 expat/lib/xmlparse.c 的脆弱源码,用于引用代码:https://github.com/libexpat/libexpat/blob/R_2_9_0/expat/lib/xmlparse.c(2026 年 11 月 2 日核对)
  • CVE-2026-115506 的 Debian 安全跟踪器,截至 2026 年 11 月 2 日将 bookworm、trixie、forky 和 sid 中的 expat 标记为存在漏洞且未修复:https://security-tracker.debian.org/tracker/CVE-2026-115506(2026 年 11 月 2 日核对)
  • CVE-2026-115506 的 Ubuntu 安全页面,列出受影响的 expat 源码包以及使用系统 expat 的软件包:https://ubuntu.com/security/CVE-2026-115506(2026 年 11 月 2 日核对)
  • CISA KEV 目录 JSON 数据源,检索四个 CVE,截至 2026 年 11 月 2 日无匹配:https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json(2026 年 11 月 2 日核对)