Automated Threat Modeling

Technical Overview of CVE-2026-116790: FreeSWITCH ICE Authentication Bypass via Unverified STUN Message Integrity

A brief summary of CVE-2026-116790, a FreeSWITCH flaw where inbound ICE connectivity checks were accepted without verifying STUN message integrity, letting a reachable peer redirect RTP and DTLS traffic. We review the root cause, the opt-in patch in version 1.11.2, and affected configurations.

CVE Analysis

9 min read

Gaurav Sarraf
Gaurav Sarraf

2026-11-05

Technical Overview of CVE-2026-116790: FreeSWITCH ICE Authentication Bypass via Unverified STUN Message Integrity
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

Before version 1.11.2, any host that could reach an active FreeSWITCH media port could forge a single STUN packet and take over where that call's RTP and DTLS traffic was being sent, without ever seeing the SDP. Because the fix ships disabled by default, an installation that has already upgraded can still accept those forged ICE connectivity checks until an operator explicitly turns verification on.

FreeSWITCH is an open-source communications engine for voice, video and messaging, maintained and sponsored by SignalWire and developed by the team that founded the company. SignalWire reports that it is used by more than 5,000 businesses and more than 300 million daily users globally. These are vendor-published figures, not independently measured market data. Its modular architecture exposes media and signaling functions for customized communications systems, which places its RTP and ICE handling underneath a wide range of SIP and WebRTC deployments, including platforms such as FusionPBX.

CVE-2026-116790 carries a CVSS 3.1 score of 9.1, assigned by GitHub as the CNA rather than by an independent NVD assessment, and is classified as CWE-290 (Authentication Bypass by Spoofing) and CWE-347 (Improper Verification of Cryptographic Signature). NVD lists the vendor as Unknown, while the official CVE record identifies it as SignalWire. This post is a brief summary of the vendor advisory, the patch, and related public reporting.

Technical information

Root cause: signing outbound, never verifying inbound

ICE relies on the STUN short-term credential mechanism. Each side advertises a username fragment and password in SDP, and every connectivity check and response carries a MESSAGE-INTEGRITY attribute: an HMAC SHA1 over the message, keyed with the relevant ICE password. That HMAC is the only thing proving the sender actually took part in the signaling exchange.

FreeSWITCH implemented half of this. It signed the STUN checks it sent with the ICE password, but it never recomputed or compared the HMAC on checks it received. Two specific gaps combined:

  1. The core STUN parser only checked the length of the MESSAGE-INTEGRITY attribute, not its value.
  2. handle_ice() in src/switch_rtp.c then advanced the ICE state machine directly from the packet's attributes.

As a result, a packet carrying an incorrect MESSAGE-INTEGRITY value, or no MESSAGE-INTEGRITY attribute at all, was processed as if it were authentic. This contradicts RFC 8445 Section 7.3, which requires an ICE agent to use the STUN short-term credential mechanism and MESSAGE-INTEGRITY to authenticate received connectivity requests, and to apply the same mechanism to responses. FreeSWITCH recognized structurally plausible packets but never confirmed that the sender possessed the session credential.

Attacker prerequisites

According to the advisory, the attacker needs only network access to the negotiated UDP media port of an active ICE call leg. The advisory describes discovering that port through a UDP scan of the configured RTP range. Exploitation explicitly requires:

  • no account on the system
  • no prior interaction with the call
  • no position on the media path
  • no ICE credentials from the SDP

Attack flow

The advisory documents the following sequence. This is the vendor's exploitation scenario, not a description of an observed incident.

  1. The attacker identifies an active ICE leg's media port, for example by scanning the RTP port range.
  2. The attacker sends a forged STUN Binding Request to that socket, with an arbitrary or missing MESSAGE-INTEGRITY attribute.
  3. FreeSWITCH processes the request's attributes without authenticating it and redirects the leg's media destination to the sender's address.
  4. FreeSWITCH marks the candidate pair as nominated. The forged request can also flip the controlling or controlled role.
  5. FreeSWITCH begins sending RTP, and its DTLS handshake, to the attacker instead of the legitimate peer, which terminates the original media path.

Impact depends on the negotiated media protection

For an unencrypted leg, the redirected stream is plaintext, so the attacker can listen to it and inject media. For encrypted media, the advisory establishes traffic redirection and loss of the legitimate path, but it does not establish that this flaw alone allows decryption. Authenticating or encrypting the signaling channel does not help here. The missing control is validation of the received ICE check on the media socket itself.

The published vector is:

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H

The scored confidentiality impact is None, even though the advisory separately describes conditional plaintext disclosure on unencrypted legs. Those are two distinct statements in the published material, and we have kept both as written.

How this relates to the other October FreeSWITCH advisories

This CVE was disclosed alongside a large batch of FreeSWITCH advisories (Enable Security's October newsletter counts 21). Two are easy to conflate with it:

  • CVE-2026-116789 is a separate DTLS certificate authentication issue. Its mitigation, rtp_dtls_client_cert_verify_mode=fingerprint, binds the peer certificate and does nothing for STUN integrity.
  • CVE-2026-116779 is a SIP MESSAGE flaw that lets api chat handling invoke API commands, including operating system commands. Enable Security confirmed exploitation of that issue on one compromised FusionPBX server.

The reviewed sources do not establish exploitation of CVE-2026-116790 itself. SecurityOnline's October 29 reporting labels this ICE flaw as not exploited, the NotCVE record (intelligence updated October 28) marks exploitation in the wild as No, and CISA KEV catalog version 2026.11.04 has no entry for it. These are dated snapshots, not proof that exploitation cannot occur, and no named actor or IOCs are associated with this CVE.

Patch information

The fix is in FreeSWITCH v1.11.2, released 9 August 2026. It came from a commit merged from a private security fork: 68ec688eb601767a28854e804a0e4df5872f5bc7. Advisory GHSA-rc25-33hp-6mv2 lists <= 1.11.1 as vulnerable and >= 1.11.2 as patched, and the v1.11.2 release notes include the entry "[core] Verify STUN MESSAGE-INTEGRITY on inbound ICE checks (opt-in)".

Upgrading alone does not close the hole. Verification is off by default. Protection only applies after setting the ice_verify_message_integrity channel variable to true, and conf/vanilla/vars.xml includes it only as a commented-out example. The commit adds 302 lines and deletes none across five files, in three parts.

1. A new verifier: switch_stun_packet_verify_integrity() (src/switch_stun.c)

The function recomputes the RFC 5389 HMAC SHA1 over the received bytes.

It works on raw wire bytes, using a private copy. switch_stun_packet_parse() converts fields to host byte order in place, so an already parsed buffer cannot be verified. The verifier copies the raw datagram (up to 1500 bytes) and leaves the caller's buffer untouched.

It bounds the attribute walk with an unsigned helper. The existing iterator macros use int16_t lengths, so a length with the high bit set could go negative and move the cursor backward. The new stun_wire_attr_bounds() helper treats lengths as unsigned and stops if an attribute would run past the end of the message:

plen = ntohs(attr->length); plen = (plen & 0x3) ? (plen & ~0x3u) + 4 : plen; if (plen > (uint32_t) (end - (const uint8_t *) attr->value)) { return SWITCH_FALSE; }

It rejects attributes appended after MESSAGE-INTEGRITY. The HMAC only covers bytes before the integrity attribute, so an attacker could take a validly signed check and append something like USE-CANDIDATE without breaking the signature. Only FINGERPRINT and MESSAGE-INTEGRITY-SHA256, both inert, are permitted afterward:

if (attr->type != htons(SWITCH_STUN_ATTR_FINGERPRINT) && attr->type != htons(SWITCH_STUN_ATTR_MESSAGE_INTEGRITY_SHA256)) { return SWITCH_STATUS_FALSE; }

It rebuilds the sender's exact HMAC input and compares in constant time. It rewrites header.length as if the message ended right after MESSAGE-INTEGRITY, so a trailing FINGERPRINT is excluded just as the sender excluded it. It then hashes the bytes up to that attribute and compares with CRYPTO_memcmp, so timing does not reveal how much of the digest matched:

HMAC(EVP_sha1(), (const unsigned char *) pass, (int) strlen(pass), copy, mi_off, digest, NULL); if (CRYPTO_memcmp(digest, mi->value, SHA_DIGEST_LENGTH) != 0) { return SWITCH_STATUS_FALSE; }

The function has three distinct return values: SUCCESS (valid HMAC), NOTFOUND (no integrity attribute) and FALSE (mismatch, wrong 20-byte length, malformed packet, or empty key). The header also adds the constant SWITCH_STUN_ATTR_MESSAGE_INTEGRITY_SHA256 = 0x001c.

2. A check at the start of handle_ice() (src/switch_rtp.c)

The check runs before last_stun or any other ICE state is touched, so a forged packet can no longer move the media destination, nominate a pair, or switch roles. The key depends on the message type:

switch (packet->header.type) { case SWITCH_STUN_BINDING_REQUEST: ikey = ice->pass; break; case SWITCH_STUN_BINDING_RESPONSE: case SWITCH_STUN_BINDING_ERROR_RESPONSE: ikey = ice->rpass; break; default: /* ... log "ignoring unauthenticated STUN" ... */ goto end; } if (switch_stun_packet_verify_integrity((const uint8_t *)data, (uint32_t)cpylen, ikey) != SWITCH_STATUS_SUCCESS) { /* ... log "MESSAGE-INTEGRITY verification failed; dropping" ... */ goto end; }

Requests must be signed with FreeSWITCH's own ICE password; responses and error responses must be signed with the peer's password. Indications (keepalives) carry no integrity and do not drive state, so they are skipped. Because the check tests != SUCCESS, a missing attribute (NOTFOUND) is dropped exactly like a bad one, which removes the simplest bypass of omitting the attribute. The check only applies to ICE_VANILLA sessions.

3. The opt-in setting

switch_rtp_activate_ice() initializes ice->verify_integrity = 0 and only enables it when the channel variable is true:

if ((type & ICE_VANILLA) && switch_channel_var_true(switch_core_session_get_channel(rtp_session->session), "ice_verify_message_integrity")) { ice->verify_integrity = 1; }

The commit message says this default leaves receive-path behaviour unchanged unless verification is enabled. That avoids breaking peers with noncompliant STUN stacks, but it also means a stock 1.11.2 install remains exposed until an operator turns it on.

Tests

tests/unit/switch_stun.c adds unit tests covering:

  • a valid HMAC passing, and the same packet failing under the wrong key
  • an all-zero integrity value being rejected
  • a trailing FINGERPRINT being tolerated
  • a missing attribute returning NOTFOUND
  • a USE-CANDIDATE appended after the integrity attribute being rejected
  • a 0x8000 attribute length stopping the walk without an out-of-bounds read (the test comment notes ASAN would catch a regression)
  • a trailing MESSAGE-INTEGRITY-SHA256 being tolerated

These tests document verifier behavior. They do not replace deployment testing. Enable Security notes that upgrading alone (it references 1.11.3) does not resolve the WebRTC authentication issues and recommends enabling the relevant options and then testing ICE and DTLS negotiation in your environment.

Interim containment

If an upgrade is not immediately possible, the vendor recommends restricting the media port range to trusted sources with a firewall, SBC or private interconnect. This limits who can deliver forged checks but does not fix authentication inside FreeSWITCH: a sender inside the perimeter can still reach the vulnerable path, and the advisory states there is no workaround within the running ICE handler. Disabling ICE is not a general option for WebRTC services, since WebRTC requires ICE.

Three states should not be confused: a patched binary (verifier present, but off), an enabled control (requires the patched binary), and a restricted perimeter (reduces reachability, leaves the flaw intact). Only the first two together form the documented remedy.

Affected systems and versions

  • Vulnerable: FreeSWITCH 1.11.1 and all earlier versions (advisory range <= 1.11.1).
  • Patched code: FreeSWITCH 1.11.2 and later (>= 1.11.2), but only effective when ice_verify_message_integrity=true is set. A default 1.11.2 install retains the vulnerable receive behavior.
  • In-scope call legs: any leg that negotiates ICE, including WebRTC through mod_verto, WebRTC through mod_sofia over WS/WSS, and SIP peers that offer ICE. No additional nondefault setting is required for the vulnerable behavior.
  • Verifier coverage: the new check applies to ICE_VANILLA sessions.
  • Impact by media type: unencrypted legs are exposed to eavesdropping and media injection. Encrypted legs are exposed to redirection and loss of the legitimate media path.

Vendor security history

The closest precedent is CVE-2021-37624. Enable Security found that FreeSWITCH versions through 1.10.6 accepted and relayed SIP MESSAGE requests without authentication by default, enabling message spoofing and spam toward registered SIP users. An auth-messages option existed, but authentication was not the default. The issue was reported on June 7, 2021 and fixed in 1.10.7, released October 24, 2021.

The two bugs share no code, but they share an operational pattern: a security control exists, yet the default deployment does not enforce the intended authentication boundary. The ICE fix makes that explicit by shipping its verification as opt-in.

For the current release, SignalWire documents broader hardening across core processing, DTLS/STUN, mod_rtmp, mod_sofia and mod_xml_rpc, and the same disclosure batch includes advisories such as a pre-authentication heap buffer overflow in core XML entity decoding and session eviction via an attacker-chosen sessid in mod_verto. SignalWire's disclosure policy directs reports to [email protected] and requests coordinated disclosure while fixes are developed.

Support tier matters for patch delivery. SignalWire states that Community Edition has no official support or maintenance and receives updates more slowly, while FreeSWITCH Enterprise provides repository access, regular updates and maintenance patching, and FreeSWITCH Advantage adds contracted technical support.

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization