Skip to content

SIPSorcery: Malformed UDP datagram crashes TurnServer receive loop with no restart, disabling TURN UDP relay for all clients (DoS)

High severity GitHub Reviewed Published Aug 10, 2026 in sipsorcery-org/sipsorcery • Updated Aug 12, 2026

Package

nuget SIPSorcery (NuGet)

Affected versions

>= 10.0.5, <= 10.0.13

Patched versions

10.0.14

Description

Summary

TurnServer.ReceiveUdpAsync places its generic catch (Exception) OUTSIDE the while receive loop, and Start() launches the loop fire-and-forget with no supervision or restart. A single pre-authentication UDP datagram whose STUN header first byte is in 0x80–0xFF causes STUNHeader.ParseSTUNHeader to throw ApplicationException, which unwinds past the loop and terminates it. The TURN UDP relay is then dead for ALL clients until the process is restarted.

Root Cause

src/SIPSorcery/net/TURN/TurnServer.cs:

  • ReceiveUdpAsync (:555-577): the inner try (:562-567) wraps only _udpSocket.ReceiveAsync(); HandleUdpDatagram(result.Buffer, result.RemoteEndPoint) (:569) is inside the while body but OUTSIDE that inner try. The generic catch (Exception ex) (:573) is lexically OUTSIDE the while.
  • Start() does _ = ReceiveUdpAsync(); (:381) — fire-and-forget, no restart.
  • HandleUdpDatagram (:579) calls STUNMessage.ParseSTUNMessage(data, data.Length) (:600) for any non-ChannelData datagram; ParseSTUNMessage (STUNMessage.cs:94) has no try/catch.

Impact

ApplicationException propagates out of the while, is caught at :573, logged, and the method returns. _running remains true but nothing re-invokes ReceiveUdpAsync → TURN UDP relay permanently unavailable for all clients (whole-server DoS). Pre-authentication: STUN parsing precedes any TURN allocation/credential check.

Proof of Concept

Send one UDP datagram to the TURN port (default 3478) with first byte 0x80 (e.g. 80 00 00 00). 0x80 & 0xC0 = 0x80 ≠ 0x40 → not ChannelData → ParseSTUNMessageParseSTUNHeader executes if ((Array[startIndex] & 0xC0) != 0) throw new ApplicationException(...) (STUNHeader.cs:169-172); 0x80 & 0xC0 = 0x80 ≠ 0 → throws.

Attack Chain

  1. Entry: one UDP datagram to the TURN port, first byte 0x80–0xFF. Guard: ChannelData branch requires (data[0] & 0xC0) == 0x40 (:583). Bypass: 0x80 & 0xC0 = 0x80 ≠ 0x40 → falls through to ParseSTUNMessage (:600).
  2. Sink: STUNMessage.ParseSTUNMessageSTUNHeader.ParseSTUNHeader (STUNHeader.cs:169-172) throws ApplicationException. Guard: none before the throw; ParseSTUNMessage has no try/catch. Bypass: 0x80 & 0xC0 = 0x80 ≠ 0 → throws.
  3. Impact: exception unwinds past the while into catch(Exception) at :573 → logged → method returns → loop exits. Guard: none — no restart (Start() :381 fire-and-forget). Bypass: N/A. TURN UDP relay dead for all clients until process restart.

Bypass Evidence

  • Loop/catch structure: catch at TurnServer.cs:573 is outside the while at :559; HandleUdpDatagram at :569 is outside the inner try (:562-567).
  • Unguarded ParseSTUNMessage at :600; throw at STUNHeader.cs:169-172.
  • Fire-and-forget start at :381 with no restart in Start().
  • TurnServerConfig.ListenAddress defaults to IPAddress.Loopback (:42), but a functioning TURN server must bind a routable address to serve clients, so real deployments are exposed. Non-default config narrows the vulnerable population, not the attack difficulty → AC:L.

Affected Versions

nuget:SIPSorcery <= 10.0.13 (TurnServer component present since 10.0.5; verified on release tag v10.0.13 and HEAD).

Dedup

NOT a duplicate of GHSA-28gm-jrmw-xx93 (CVE-2026-54632), which covers the client RTP/ICE socket (UdpReceiver/RTPChannel). TurnServer is a distinct shipped RFC 5766 server component with its own loop and fix location.

Suggested Fix

Wrap HandleUdpDatagram in a per-datagram try/log-and-continue INSIDE the while (matching the drop-and-continue intent of fix bdb76cb), and/or add loop supervision/restart.


Reported by zx (Jace) — GitHub: @manus-use

References

@sipsorcery sipsorcery published to sipsorcery-org/sipsorcery Aug 10, 2026
Published to the GitHub Advisory Database Aug 12, 2026
Reviewed Aug 12, 2026
Last updated Aug 12, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

EPSS score

Weaknesses

Uncaught Exception

An exception is thrown from a function, but it is not caught. Learn more on MITRE.

Improper Handling of Exceptional Conditions

The product does not handle or incorrectly handles an exceptional condition. Learn more on MITRE.

CVE ID

No known CVE

GHSA ID

GHSA-pfvm-w89x-94jw

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.