CVE-2024-24549 — Apache Tomcat HTTP/2 延迟请求头限制校验拒绝服务漏洞¶
发布于 2026-06-04
拒绝服务攻击——无需认证
未经认证的远程攻击者可通过发送包含超大或超量请求头的 HTTP/2 请求来耗尽服务器资源。服务器必须完整解析整个请求头块后才能拒绝该流,而非在检测到超限时立即停止处理。
| 字段 | 值 |
|---|---|
| 项目 | Apache Tomcat |
| 严重级别 | HIGH |
| CVSS v3.1 | 7.5 HIGH (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) |
| CWE | CWE-20(输入验证不当) |
| 受影响版本 | 8.5.0–8.5.98、9.0.0-M1–9.0.85、10.1.0-M1–10.1.18、11.0.0-M1–11.0.0-M16 |
| 修复版本 | 8.5.99、9.0.86、10.1.19、11.0.0-M17 |
| GHSA | GHSA-7w75-32cg-r6g2 |
1. 漏洞概述¶
Apache Tomcat 是一个广泛部署的 Java Servlet 容器,在支持 HTTP/1.1 的同时也支持 HTTP/2。当客户端发送 HTTP/2 请求时,请求头可能分布在多个帧中:一个 HEADERS 帧(可能不包含 END_HEADERS 标志),之后跟随一个或多个 CONTINUATION 帧。Tomcat 通过连接器配置 maxHttpHeaderSize 和 maxHeaderCount 来限制请求头的总大小和数量。
CVE-2024-24549 是一个拒绝服务漏洞,根源在于限制校验的时机不当。能够访问 HTTP/2 监听器的攻击者可以发送一个请求头块,将其拆分到一个 HEADERS 帧和一个或多个 CONTINUATION 帧中,使单个 HEADERS 帧已足以超出配置的限制。Tomcat 将限制检查推迟到请求头块末尾——等到所有 CONTINUATION 帧完全读取并经过 HPACK 解码之后——因此服务器被迫缓冲并处理超限块的每一个字节,才能发出流重置信号。在大量并发流或连接的场景下,这会导致资源耗尽。无需认证;HTTP/2 必须处于启用状态(在使用 NIO2/APR 连接器的 Tomcat 中默认启用)。
根本原因¶
文件: java/org/apache/coyote/http2/Http2Parser.java
函数: readHeadersFrame()、readContinuationFrame()、onHeadersComplete()
在存在漏洞的版本中,validateHeaders() 仅在 onHeadersComplete() 中被调用一次,而该方法只有在请求头块的最后一帧(携带 END_HEADERS 的帧)被消费后才会执行。原始代码中的注释表明这是有意为之,理由是认为 HTTP/2 规范要求接收方在拒绝请求之前必须读取(吞掉)请求头块中的所有帧:
// BEFORE (in onHeadersComplete — called only after ALL header frames received)
private void onHeadersComplete(int streamId) throws Http2Exception {
// ...
// Delay validation (and triggering any exception) until this point
// since all the headers still have to be read if a StreamException is
// going to be thrown.
hpackDecoder.getHeaderEmitter().validateHeaders(); // <-- deferred to end
output.headersEnd(streamId, headersEndStream);
// ...
}
修复方案将 validateHeaders() 移至每个单独帧的末尾调用:在 readHeadersFrame 完成吞掉填充数据后立即调用,在每个 readContinuationFrame 完成读取负载后再次调用。若在请求头块中途检测到超限,流现在会在帧边界处被重置,而非等到整个块处理完毕。修正后的注释也明确说明,HTTP/2 规范实际上允许接收方在请求头块中的任意单个帧之后发送 RST_STREAM;原有的"必须先读取所有帧"的认识是错误的。
// AFTER — in readHeadersFrame(), after swallowPayload():
swallowPayload(streamId, FrameType.HEADERS.getId(), padLength, true);
// Validate the headers so far
hpackDecoder.getHeaderEmitter().validateHeaders(); // <-- early check added
if (Flags.isEndOfHeaders(flags)) {
onHeadersComplete(streamId);
} else { ...
// AFTER — in readContinuationFrame(), after readHeaderPayload():
readHeaderPayload(streamId, payloadSize);
// Validate the headers so far
hpackDecoder.getHeaderEmitter().validateHeaders(); // <-- early check added
if (endOfHeaders) {
headersCurrentStream = -1;
onHeadersComplete(streamId);
...
// AFTER — removed from onHeadersComplete():
- // Delay validation (and triggering any exception) until this point
- // since all the headers still have to be read if a StreamException is
- // going to be thrown.
- hpackDecoder.getHeaderEmitter().validateHeaders();
2. 漏洞复现环境¶
复现环境使用官方未经修改的 tomcat:10.1.18-jdk17-temurin-jammy Docker 镜像运行单个容器。Tomcat 10.1.18 处于受影响范围(10.1.0-M1 至 10.1.18)内,包含存在漏洞的 Http2Parser 代码。镜像不作任何源码修改,仅通过挂载两个配置文件来建立验证所需的诊断条件。
下载完整环境包:env.zip
独立文件:
环境拓扑¶
| 组件 | 详情 |
|---|---|
| 容器 | cve-2024-24549-tomcat(镜像 tomcat:10.1.18-jdk17-temurin-jammy) |
| 协议 | HTTP/1.1 + HTTP/2 明文(h2c),共用同一端口 |
| 暴露端口 | 127.0.0.1:8080 → 容器 8080 |
| 传输层 | 无 TLS,无 ALPN;通过 HTTP/2 先验知识(prior-knowledge)前导码使用 h2c |
| 认证 | 无需认证 |
env/config/server.xml 在端口 8080 上配置了一个 NIO 连接器,设置 maxHttpHeaderSize="8192"(8 KB),同一端口上附加 Http2Protocol 升级协议,maxHeaderCount="20"。HTTP/2 解析器从所属连接器继承请求头大小限制,因此两个限制均较为保守且无歧义:超过 8 KB 的请求头块或包含超过 20 个请求头的请求均属超限。环境中没有上游代理或其他会预先校验、规范化请求头的组件。
env/config/logging.properties 将 org.apache.coyote.http2 日志记录器级别提升至 FINE(调试级别)。这样一来,Http2UpgradeHandler 在流被拒绝时会将完整的 StreamException 及其堆栈跟踪同时输出到容器标准输出和 Catalina 日志文件中。这两个输出均由服务器控制,与漏洞利用进程无关,构成确认漏洞行为的诊断通道。
启动环境¶
等待健康检查通过(最多 60 秒,每 5 秒轮询一次),然后确认版本和 h2c 是否就绪:
docker inspect -f '{{.State.Health.Status}}' cve-2024-24549-tomcat
# expect: healthy
curl -s --http2-prior-knowledge -o /dev/null \
-w 'http_version=%{http_version} code=%{response_code}\n' \
http://127.0.0.1:8080/
# expect: http_version=2 code=404
code=404 是预期结果——没有部署任何 Web 应用程序。关键是 http_version=2 确认了 h2c 协商成功,HTTP/2 解析器(包括存在漏洞的代码路径)处于活跃状态。
确认诊断通道正在输出 HTTP/2 级别的日志:
3. 漏洞利用方法¶
漏洞利用脚本发送一个 HTTP/2 流,其请求头块被故意拆分到两个帧:一个不带 END_HEADERS 的 HEADERS 帧(已超出 8192 字节限制),短暂暂停后再发送一个带 END_HEADERS 的 CONTINUATION 帧。在存在漏洞的版本上,服务器会读取并 HPACK 解码这两个帧后才重置流。在已修复的版本上,流在 HEADERS 帧之后立即被重置,CONTINUATION 帧不会被消费。
概念验证脚本 exploit/h2_continuation_dos.py 仅使用 Python 标准库手动构造 HTTP/2 帧,无需任何外部依赖。下载完整漏洞利用包:exploit.zip
步骤一——建立干净的基线¶
运行漏洞利用之前,先确认当前尚无流错误:
步骤二——运行漏洞利用脚本¶
三个参数分别为:目标主机、端口,以及 HEADERS 帧和 CONTINUATION 帧之间的暂停秒数(0.5 秒可使帧边界在服务器日志时间戳中清晰可辨)。脚本将其发送和接收的帧输出到标准错误,供诊断参考;但成功与否的判定依据来自服务器日志,而非脚本输出。
脚本的诊断输出类似如下:
[*] connected to 127.0.0.1:8080
[*] sent preface + SETTINGS + SETTINGS ACK
[*] sent HEADERS (no END_HEADERS) stream=1 junk_header_value_bytes=12000 payload_len=12231
[*] sent CONTINUATION (END_HEADERS) AFTER the over-limit HEADERS frame
[recv] frame=SETTINGS flags=0x00 stream=0 len=18
[recv] frame=SETTINGS flags=0x01 stream=0 len=0
[recv] frame=RST_STREAM flags=0x00 stream=1 len=4 error_code=11
[*] done
步骤三——读取服务器端证据¶
决定性证据来自服务器自身的诊断通道(Docker 日志),而非漏洞利用脚本的标准输出。漏洞利用完成后立即读取:
docker logs cve-2024-24549-tomcat 2>&1 | \
grep -E 'Frame type \[HEADERS\]|Frame type \[CONTINUATION\]|swallowPayload|Closed due to error|StreamException|readHeaderPayload.*payload of size' | tail -8
漏洞成功触发的证明¶
服务器日志记录了流 1 上的完整事件序列。关键的可观测点不是流最终被拒绝,而是重置发生在哪个帧之后——这才是区分漏洞版本与已修复版本行为的判别依据。
本次运行在连接 [6] 上产生的日志呈现出以下事件序列:
| 时间 | 服务器日志事件 | 含义 |
|---|---|---|
04:34:43.474 |
Http2Parser.validateFrame ... Frame type [HEADERS], Flags [0], Payload size [12231] |
收到超限 HEADERS 帧(12231 > 8192 限制),无 END_HEADERS |
04:34:43.475 |
readHeaderPayload ... Processing headers payload of size [12,231] |
完整 HEADERS 块被读取并进行 HPACK 解码 |
04:34:43.475–.477 |
Stream.emitHeader ... x-junk-00 .. x-junk-07(全部垃圾请求头) |
超限块中的每个请求头均被解码 |
04:34:43.478 |
Http2Parser.swallowPayload ... Swallowed [0] bytes 然后 upgradeDispatch Exit ... SocketState [ASYNC_IO] |
HEADERS 帧处理完毕——处理程序返回等待更多帧,未发出任何重置 |
04:34:44.123 |
validateFrame ... Frame type [CONTINUATION], Flags [4], Payload size [217] + readHeaderPayload ... size [217] |
服务器消费了在超限已发生 0.645 秒后才发送的 CONTINUATION 帧 |
04:34:44.124 |
Http2UpgradeHandler.upgradeDispatch ... Stream [1] Closed due to error |
流重置仅在此时触发 |
04:34:44.125 |
sendStreamReset ... Error [ENHANCE_YOUR_CALM], Message [... Total header size too big] |
RST_STREAM 在 END_HEADERS 时或之后发出 |
日志中的 StreamException 堆栈跟踪显示,异常对象是在处理 HEADERS 帧期间构造的(Http2Parser.java:543,由 readHeadersFrame 第 282 行调用),而实际的流重置(Closed due to error)却发生在半秒多之后的 CONTINUATION 帧分发阶段。由于 Java 在对象构造时记录堆栈,这种"HEADERS 帧时构造异常、CONTINUATION 帧后才重置"的分裂现象直接证明了延迟校验代码路径的存在:异常被暂存,在 onHeadersComplete() 内部才重新抛出。
org.apache.coyote.http2.StreamException: Connection [6], Stream [1], Total header size too big
at org.apache.coyote.http2.Http2Parser.readHeaderPayload(Http2Parser.java:543)
at org.apache.coyote.http2.Http2Parser.readHeadersFrame(Http2Parser.java:282)
...
已修复的版本(10.1.19 及以上)会在 readHeadersFrame 末尾调用 validateHeaders(),在 04:34:43.478 处发出重置,日志中不会出现任何 Frame type [CONTINUATION] 行。对已修复服务器运行相同的脚本,不会有任何 CONTINUATION 帧被处理——这是将上述行为归因于本漏洞的依据。
环境清理¶
测试完成后,停止并移除容器及其网络:
4. 安全建议¶
修复方案¶
将 Apache Tomcat 升级到包含修复的版本:
| 分支 | 最低安全版本 |
|---|---|
| 8.5.x | 8.5.99 |
| 9.0.x | 9.0.86 |
| 10.1.x | 10.1.19 |
| 11.0.x | 11.0.0-M17 |
修复方案将 validateHeaders() 从 onHeadersComplete() 移至 readHeadersFrame() 和 readContinuationFrame() 中,使其在每个单独帧末尾运行,而非在整个请求头块末尾运行。该修复无需任何配置变更,完全体现在已修复的二进制文件中。
缓解措施与临时方案¶
如果无法立即升级,可考虑以下措施:
- 禁用 HTTP/2。 从
server.xml中移除<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" .../>元素。该漏洞特定于 HTTP/2 解析器,HTTP/1.1 不受影响。此措施可完全消除攻击面,代价是失去 HTTP/2 功能。 - 降低请求头限制。 降低
maxHttpHeaderSize和maxHeaderCount并不能阻止攻击,但可以减少服务器在拒绝每个流之前必须处理的数据量,降低单流开销;延迟校验逻辑本身不受影响。 - 在前端部署反向代理或进行速率限制。 在 Tomcat 前放置终止 HTTP/2 连接的反向代理或 WAF,可降低暴露面,具体效果取决于该组件自身的 HTTP/2 处理方式。
以上措施均不能消除底层漏洞,升级是唯一根本性的修复方式。
参考资料¶
- NVD CVE-2024-24549 — CWE、CVSS、漏洞描述
- GHSA-7w75-32cg-r6g2 — 按 Maven 构件列出的受影响/已修复版本
- Apache 邮件列表公告 — 官方披露
- 补丁提交 0cac540 (Tomcat 9.x / 9.0.86)
- 补丁提交 810f49d (Tomcat 10.x / 10.1.19)
- 补丁提交 8e03be9 (Tomcat 11.x / 11.0.0-M17)
- 补丁提交 d07c821 (Tomcat 11.x 附加修复)
- Apache Tomcat 8 安全页面
- Apache Tomcat 9 安全页面
- Apache Tomcat 10 安全页面
- Apache Tomcat 11 安全页面
- OSS-Security 公告
- Snyk 博客:HTTP/2 CONTINUATION DoS — 同类漏洞分析
- NetApp 安全公告 NTAP-20240402-0002 — 下游厂商影响