跳转至

CVE-2024-53271 — Envoy BALSA 解析器双重 MessageDone() 拒绝服务漏洞

发布于 2026-06-06

概述

Envoy 的 HTTP/1.1 BALSA 解析器中存在一个逻辑错误:当上游服务器发送非 101 的 1xx 中间响应时,MessageDone() 会被同一流调用两次,破坏连接状态,导致大量下游请求以流重置错误终止。攻击者无需任何凭据即可从远程触发该问题。

字段
Project Envoy Proxy
受影响组件 source/common/http/http1/balsa_parser.ccBalsaParser::MessageDone()
Severity HIGH
CVSS AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H(v3.1,评分 7.1)
CWE CWE-670
受影响版本 1.31.0 – 1.31.4,1.32.0 – 1.32.2
修复版本 1.31.5,1.32.3
安全公告 GHSA-rmm5-h2wv-mg4f

1. 漏洞概述

Envoy Proxy 是一款用 C++ 编写的高性能边缘与服务代理。自大约 1.26 版本起,Envoy 默认使用 BALSA 作为 HTTP/1.1 解析器。CVE-2024-53271 是 BALSA 中的一个控制流缺陷:攻击者可通过安排上游 HTTP/1.1 服务器在最终响应前发送一个非 101 的 1xx 中间响应(例如 102 Processing)来触发该漏洞。当运行时特性标志 envoy.reloadable_features.http1_balsa_delay_reset 为启用状态(在所有受影响版本中均为默认值)时,该缺陷会导致 MessageDone() 在同一流上被调用两次,破坏 Envoy 的逐流解析器状态。结果是大量下游客户端请求以流重置或连接错误终止——在典型条件下约占 65%,在本次复现的高并发场景下约占 96–100%。

根本原因。source/common/http/http1/balsa_parser.cc 中,BalsaParser::MessageDone() 通过调用 connection_->onMessageComplete() 来结束单个 HTTP 消息。对于没有消息体的 1xx 响应,BALSA 解析器会从 HeaderDone() 直接进入 MessageDone(),第一次调用将 first_byte_processed_ 重置为 false。当最终响应(例如 200 OK)随后到来时,MessageDone() 被再次调用。函数顶部的守卫仅检查 ParserStatus::Error,因此两次调用均执行了 onMessageComplete(),破坏了连接和流状态。

该补丁添加了提前返回条件,使 MessageDone() 变为幂等函数:

// BEFORE
void BalsaParser::MessageDone() {
-  if (status_ == ParserStatus::Error) {
+  if (status_ == ParserStatus::Error ||
+      // In the case of early 1xx, MessageDone() can be called twice in a row.
+      // The !first_byte_processed_ check is to make this function idempotent.
+      (wait_for_first_byte_before_msg_done_ && !first_byte_processed_)) {
     return;
   }
   status_ = convertResult(connection_->onMessageComplete());

补丁同时引入了新的运行时守卫 envoy.reloadable_features.wait_for_first_byte_before_balsa_msg_done,在已修复版本中默认启用。由于第一次调用后 first_byte_processed_false,第二次 MessageDone() 调用会提前返回,跳过对 onMessageComplete() 的第二次调用。

2. 漏洞环境

本次复现完全在 Docker 中运行,使用 Envoy 官方预编译镜像,无需从源代码编译。四个容器共享同一桥接网络(cve-2024-53271-net):

容器 角色 镜像 宿主机端口
cve-2024-53271-envoy-vuln 存在漏洞的代理 envoyproxy/envoy:v1.32.2 127.0.0.1:10000(下游),127.0.0.1:9900(管理)
cve-2024-53271-envoy-baseline 已修复的基线代理 envoyproxy/envoy:v1.32.3 127.0.0.1:10001(下游),127.0.0.1:9901(管理)
cve-2024-53271-upstream-102 触发上游 自定义(cve-2024-53271-upstream:local 仅内部可达
cve-2024-53271-upstream-plain 存活检测上游(无 1xx) 自定义(cve-2024-53271-upstream:local 仅内部可达

每个代理在其单一 :10000 监听器上暴露两条路由:

  • GET|HEAD /trigger — 路由至 upstream-102,后者先回复原始 102 Processing 帧,再回复 200 OK。这是在存在漏洞的代理上触发该缺陷的路径。
  • GET|HEAD /plain — 路由至 upstream-plain,后者仅回复 200 OK(无 1xx)。此路径用作存活检测,独立于漏洞来确认代理正常运行。

存在漏洞的代理(envoy-vuln)在 env/config/envoy-vuln.yaml 中显式设置了两个触发性运行时标志:

layered_runtime:
  layers:
  - name: static_layer
    static_layer:
      envoy.reloadable_features.http1_balsa_delay_reset: true
      envoy.reloadable_features.wait_for_first_byte_before_balsa_msg_done: false

基线代理(envoy-baseline)运行 envoyproxy/envoy:v1.32.3,两个标志均设置为相反方向(http1_balsa_delay_reset: falsewait_for_first_byte_before_balsa_msg_done: true)。其监听器、路由和集群配置与存在漏洞的代理在字节级别完全一致,以确保差异对比有效。

环境文件可通过 env.zip 整包下载。单独文件:env/Dockerfileenv/docker-compose.ymlenv/config/envoy-vuln.yamlenv/config/envoy-baseline.yamlenv/config/upstream.py

启动环境:

docker compose -f env/docker-compose.yml up -d --build

运行漏洞利用脚本前,请确认环境健康(以下四个请求均应返回 200):

docker compose -f env/docker-compose.yml ps
curl -s -o /dev/null -w "vuln admin: %{http_code}\n"     http://127.0.0.1:9900/ready
curl -s -o /dev/null -w "baseline admin: %{http_code}\n" http://127.0.0.1:9901/ready
curl -s -o /dev/null -w "vuln /plain:     %{http_code}\n" http://127.0.0.1:10000/plain
curl -s -o /dev/null -w "baseline /plain: %{http_code}\n" http://127.0.0.1:10001/plain

验证确切的镜像版本:

docker exec cve-2024-53271-envoy-vuln     envoy --version   # .../1.32.2/...
docker exec cve-2024-53271-envoy-baseline envoy --version   # .../1.32.3/...

3. 漏洞利用方法

漏洞利用采用差异化探测方式:脚本向存在漏洞和已修复的两个代理同时发送并发 HTTP/1.1 请求,路径均为 /trigger(路由至发送 102 响应的上游),测量有多大比例的请求能够达到干净的流结束状态。攻击者只需是一个普通的、无需任何特权的未认证下游客户端。

漏洞利用脚本可通过 exploit.zip 整包下载。单独文件:exploit/run.shexploit/probe.py

第 1 步。 确认环境已启动并健康(四个 200 响应,如第 2 节所示)。

第 2 步。 运行漏洞利用脚本:

bash exploit/run.sh 127.0.0.1 10000 127.0.0.1 10001 /trigger /plain 50 20

各参数依次为:存在漏洞的代理主机、其端口、基线代理主机、其端口、触发路径、存活检测路径、每个配置每条路径的探测数量(50 次)以及并发数(同时处于发送状态的探测 20 个)。脚本首先通过 /plain 对两个代理执行存活预检,然后向每个代理并发发送 50 个 /trigger 探测请求,读取原始下游 HTTP/1.1 字节,将每个响应分类为正常或异常,打印每个配置的干净完成率,并从 :9900:9901 读取 Envoy 管理员统计数据作为独立佐证。

如何证明漏洞已成功触发。 依据来自两项独立观测,均非漏洞利用脚本自身的输出声明。

其一,漏洞利用脚本通过读取原始下游网络字节对每个探测分类,检查响应是否为带有有效最终状态行的完整 HTTP 消息。在存在漏洞的代理上,被破坏的解析器发出的帧内容被脚本报告为 bad-status-line: b'0'empty-response (stream reset / decoder reset),即 Envoy 在 MessageDone() 被调用两次后发出的乱码字节。该观测来自下游客户端视角,而非脚本伪造。

其二,Envoy 自身的管理员统计端点(/stats)独立于任何客户端,追踪 http.ingress_http.downstream_rq_completeddownstream_rq_total 计数器。复现运行结束后,这些计数器证实了如下失败情况:

VULN:     http.ingress_http.downstream_rq_completed: 51  / downstream_rq_total: 101
BASELINE: http.ingress_http.downstream_rq_completed: 101 / downstream_rq_total: 101

在存在漏洞的代理上,101 个请求中仅有 51 个(50 个 /plain 存活探测加上 /trigger 批次中的一个)按 Envoy 自身内部计数器达到了干净完成,剩余 50 个 /trigger 探测始终未能完成。在已修复的代理上,已完成数等于总数。

两次连续运行的实际观测输出:

# Run 1
RESULT VULN-LIVENESS:     clean=50 failed=0  total=50 clean_rate=100.0%
RESULT BASELINE-LIVENESS: clean=50 failed=0  total=50 clean_rate=100.0%
RESULT VULN-TRIGGER:      clean=0  failed=50 total=50 clean_rate=0.0%   (all "bad-status-line: b'0'")
RESULT BASELINE-TRIGGER:  clean=50 failed=0  total=50 clean_rate=100.0%

# Run 2 (reproducibility)
RESULT VULN-LIVENESS:     clean=50 failed=0  total=50 clean_rate=100.0%
RESULT BASELINE-LIVENESS: clean=50 failed=0  total=50 clean_rate=100.0%
RESULT VULN-TRIGGER:      clean=2  failed=48 total=50 clean_rate=4.0%
RESULT BASELINE-TRIGGER:  clean=50 failed=0  total=50 clean_rate=100.0%

存活路径(/plain,无 1xx 上游)在每次运行中对两个代理均以 100% 干净完成,证实代理基础设施本身正常,失败仅出现在 1xx-then-final 触发条件下。已修复代理上的 /trigger 路径同样以 100% 完成,说明同一上游序列在已打补丁的构建中可被正确处理。只有在启用了触发性标志的存在漏洞构建上,干净完成率才降至 0–4%,可将该失败归因于本 CVE 而非环境因素。

清理环境。 完成后,停止并删除所有容器和卷:

docker compose -f env/docker-compose.yml down -v

4. 安全建议

修复措施。 升级至 Envoy 1.31.5 或 1.32.3。这两个版本引入了运行时守卫 envoy.reloadable_features.wait_for_first_byte_before_balsa_msg_done(默认启用),使 MessageDone() 变为幂等函数,阻止第二次调用在流已完成 1xx 阶段后再次调用 onMessageComplete()

临时缓解措施。 如无法立即升级,可在分层运行时配置中将运行时特性 envoy.reloadable_features.http1_balsa_delay_reset 设置为 false,禁用延迟重置行为——该行为是触发双重 MessageDone() 调用的前提条件。此标志可能存在功能上的权衡,在生产环境中部署前请查阅发布说明和安全公告。

影响范围。 该漏洞仅影响通过 BALSA 解析器(自 ~1.26 版本起为默认解析器)处理的 HTTP/1.1 流量,且仅限于上游可发送非 101 的 1xx 响应的路径。HTTP/2 和 HTTP/3 连接不受影响。CVSS 中的 UI:R 分量反映了攻击者需要控制一个能发送 1xx 的上游服务;在服务网格场景中,一个被攻陷或受攻击者控制的上游即可满足此条件。

参考资料。