Neutron, our AI engine, scored 97.1% on UC Berkeley's CyberGym benchmark. Learn more

Deep Dive

Deep Dive

FreeSWITCH SIP MESSAGE RCE, Exploited in the Wild (CVE-2026-116779)

A SIP MESSAGE to a stock FreeSWITCH profile can run FreeSWITCH API and OS commands; fixed in 1.11.3 and exploited in the wild.

TL;DR

  • A crafted SIP MESSAGE to a FreeSWITCH profile can select the api chat protocol and run FreeSWITCH API commands, including operating-system commands, as the FreeSWITCH process. SignalWire published CVE-2026-116779 on 2026-11-05.
  • Every release up to 1.11.2 is affected. Stock internal and external profiles accept inbound MESSAGE by default, and a stock default directory user with no password can make the attack unauthenticated.
  • The advisory scores it CVSS v3.1 9.8 Critical and states that exploitation of this path has been observed in the wild. The CVE is not in CISA KEV as of 2026-11-06.
  • Upgrade to FreeSWITCH 1.11.3 or later. If you cannot, set enable-chat=false on profiles that do not need SIP chat; see "How do I fix it?".

Am I affected?

You are exposed only when all of these hold:

  • The host runs FreeSWITCH 1.11.2 or earlier.
  • At least one SIP profile accepts inbound MESSAGE. Chat handling is on unless the profile sets enable-chat=false, and the stock conf/vanilla internal and external profiles leave it on.
  • The attacker can reach that profile over SIP, or can deliver a MESSAGE through a trunk or peer that can.
  • The profile does not already reject the api chat protocol. Releases before 1.11.3 have no enable-chat-api-proto setting, so the route is available on every profile that accepts MESSAGE.

MESSAGE authentication changes who can reach the route but does not remove it. Since FreeSWITCH 1.10.7, auth-messages is on by default, so any account on the profile is enough: an extension, a handset, or a trunk peer that can register. The stock conf/vanilla/directory/default/default.xml defines a default user with no password and no a1-hash; a directory user with neither authenticates unless allow-empty-password is false for it. That makes the stock configuration unauthenticated.

The inbound ACL does not cover MESSAGE, so tightening the ACL does not take a profile out of scope. A profile with enable-chat=false does not reach the vulnerable handler.

Branch Affected versions Fixed version
FreeSWITCH 1.11 1.11.0 to 1.11.2 1.11.3 (released 2026-08-28)
FreeSWITCH 1.10 and earlier every release before 1.11.3 1.11.3

Check what the host runs and which profiles accept chat:

# FreeSWITCH version
fs_cli -x "version"
# If fs_cli is unavailable, run the binary directly
freeswitch -version

# Profiles and whether inbound chat is disabled
grep -R "enable-chat" /etc/freeswitch/sip_profiles/ 2>/dev/null

# Directory users with no password or a1-hash
grep -R -E "<user id=|<param name=\"(password|a1-hash)\"" /etc/freeswitch/directory/ 2>/dev/null

A profile with no enable-chat=false line is in scope. The default user is the one to look for first: if it has neither password nor a1-hash, a MESSAGE can reach the route without an account.

How do I fix it?

  1. Upgrade to FreeSWITCH 1.11.3 or later. Use the SignalWire package repository or rebuild from the v1.11.3 tag, then restart FreeSWITCH. The release notes list the new enable-chat-api-proto parameter and the bound on the To-user split.
  2. If you build your own packages, rebuild every node that runs mod_sofia and mod_dptools; the fix is in both modules and in the vanilla profile samples.
  3. If you cannot upgrade, disable inbound chat on every profile that does not need it: <param name="enable-chat" value="false"/> Add the line to each profile in /etc/freeswitch/sip_profiles/, then reload the profile or restart FreeSWITCH. This closes the route because the handler returns before it parses the protocol selector. The cost is that the profile stops delivering all inbound SIP MESSAGE chat, not only the api protocol.
  4. If you need chat and cannot upgrade, remove every directory user that has no password or a1-hash, and set allow-empty-password to false for the rest. Combined with auth-messages, this reduces the attack to an authenticated user; it does not close the route, because any account can still select api.
  5. disable_system_api_commands=true removes the system API but not the route. The attacker can still reach every other registered API, including module loading and call origination. Treat it as a narrowing measure, not a fix.

What is the root cause?

The SIP MESSAGE handler treats the left side of the To user as a chat protocol name and the right side as the address, and the api chat protocol executes its address as a FreeSWITCH API command. In src/mod/endpoints/mod_sofia/sofia_presence.c, sofia_presence_handle_sip_i_message() does this:

if ((p = strchr(to_user, '+')) && p != to_user) {
    switch_copy_string(proto, to_user, sizeof(proto));
    p = strchr(proto, '+');
    *p++ = '\0';

    if ((to_addr = strdup(p))) {
        if ((p = strchr(to_addr, '+'))) {
            *p = '@';
        }
    }
} else {
    to_addr = switch_mprintf("%s@%s", to_user, to_host);
}

It then puts proto in the to_proto header and to_addr in the to header of a SWITCH_EVENT_MESSAGE. Nothing restricts which protocol name the sender may choose. mod_dptools registers the api chat interface next to the event one:

SWITCH_ADD_CHAT(chat_interface, "event", event_chat_send);
SWITCH_ADD_CHAT(chat_interface, "api", api_chat_send);

api_chat_send() takes the to header, URL-decodes it, splits it at the first space, and passes the two parts to switch_api_execute():

if (to) {
    char *v = NULL;
    switch_stream_handle_t stream = { 0 };
    char *cmd = NULL, *arg;

    if (!(v = switch_core_get_variable_dup(to))) {
        v = strdup(to);
    }

    cmd = v;
    switch_assert(cmd);

    switch_url_decode(cmd);

    if ((arg = strchr(cmd, ' '))) {
        *arg++ = '\0';
    }

    SWITCH_STANDARD_STREAM(stream);
    switch_api_execute(cmd, arg, NULL, &stream);

A MESSAGE to sip:api+system%20id@example.com sets proto to api and to_addr to system%20id. api_chat_send() decodes the address to system id, splits it into the command system and the argument id, and runs it. The output is sent back as the chat reply.

How is it exploited?

  1. The attacker sends a SIP MESSAGE to a profile that accepts inbound chat, addressed to sip:api+system%20id@target or to another API command encoded the same way.
  2. If the profile enforces MESSAGE authentication, the attacker uses any account on the profile, or the stock default user that has no password and no a1-hash.
  3. sofia_presence_handle_sip_i_message() splits the To user at the first +: the left side becomes to_proto=api, the right side becomes to=system%20id.
  4. The chat layer dispatches the message to the api interface registered by mod_dptools.
  5. api_chat_send() URL-decodes system%20id, splits it into system and id, and calls switch_api_execute("system", "id").
  6. The command runs with the FreeSWITCH process's privileges. The attacker can run other system commands, load modules, originate calls, or read configuration and credentials; where mod_lua is loaded, inline Lua with the full standard library is a second sink.

The advisory states that exploitation of this path has been observed in the wild. It gives no date, victim count, command sequence or indicators, and no public proof-of-concept code is listed.

How does the attack flow?

A SIP MESSAGE whose To user starts with api+ selects the command-execution chat protocol, and the address after the plus is run as a FreeSWITCH API command.

flowchart LR A["SIP MESSAGE to api+system%20id@target"] --> B["sofia_presence_handle_sip_i_message splits the To user"] B --> C["to_proto=api, to=system%20id"] C --> D["api_chat_send URL-decodes and calls switch_api_execute"] D --> E["OS command runs as FreeSWITCH"]

What can an attacker do?

An attacker who can deliver one accepted MESSAGE to an affected profile can run FreeSWITCH API commands with the privileges of the FreeSWITCH process. That includes:

  • Operating-system commands through the system API, which runs them as the FreeSWITCH user.
  • Configuration and credential disclosure, including SIP account and trunk credentials the process can read.
  • Full call control: originating, bridging, eavesdropping and tearing down calls.
  • Module loading and other registered APIs, which can extend the attacker's reach beyond the system command.
  • Inline Lua execution where mod_lua is loaded, as stock configuration does, with the full Lua standard library.

The conditions are listed in full: a reachable SIP profile that accepts MESSAGE, enable-chat not false, and either MESSAGE authentication satisfied by any account or a stock directory user with no password and no a1-hash. The process usually runs as an unprivileged freeswitch user; the advisory does not claim root. The impact is still full control of the telephony service and the data it can read.

Why does the fix work?

The fix adds a per-profile flag that is off by default and rejects the api protocol before the message is dispatched:

+               if (!strcasecmp(proto, "api") && !sofia_test_pflag(profile, PFLAG_ENABLE_CHAT_API_PROTO)) {
+                   /* p is the rest of the To user, still percent-encoded and bounded by the proto buffer. */
+                   switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING,
+                                     "Profile [%s] rejected a MESSAGE from %s@%s (%s:%d) addressed to the 'api' chat proto [%s]. "
+                                     "Set enable-chat-api-proto=true on the profile to permit it.\n",
+                                     profile->name, switch_str_nil(from_user), switch_str_nil(from_host), network_ip, network_port, p);
+
+                   nua_respond(nh, SIP_403_FORBIDDEN, NUTAG_WITH_THIS_MSG(de->data->e_msg), TAG_END());
+
+                   if (full_from) {
+                       su_free(nua_handle_get_home(nh), full_from);
+                   }
+
+                   return;
+               }

The comparison is case-insensitive to match the chat registry, which is created case-insensitively. A MESSAGE that selects api on a profile without the flag is answered 403 and logged with the source and the requested command. Messages with no protocol selector, or with another chat protocol, route unchanged. The flag ships commented out in the four vanilla profiles and the mod_sofia sample, so the default is closed. The fix is complete for this route; it does not remove the api chat interface, but it gates it behind an explicit opt-in.

How do I check if I was compromised?

No indicators of compromise are published. The checks in this section find the request pattern and the configuration that made it reachable, not a confirmed compromise:

# MESSAGE requests whose To user selects the api protocol
sudo grep -iE "MESSAGE.*api\+" /var/log/freeswitch/freeswitch.log

# On 1.11.3 and later, rejected attempts are logged with this text
sudo grep -i "addressed to the 'api' chat proto" /var/log/freeswitch/freeswitch.log

# Whether mod_lua is loaded, which adds the inline-Lua sink
fs_cli -x "module_exists mod_lua"

Before 1.11.3, a successful request may leave no warning, and the log may record only a normal MESSAGE. Check for unexpected outbound calls, new files in the FreeSWITCH spool or script directories, changed dialplans, new SIP registrations and any command output the attacker could have received. If you cannot rule out a successful request, treat the host as compromised, rebuild it, rotate the SIP and trunk credentials, and review the call detail records.

Timeline

  • 2026-08-26: the fix is committed as PR #3135, adding enable-chat-api-proto and the 403 response.
  • 2026-08-28: FreeSWITCH 1.11.3 is released with the fix.
  • 2026-10-28: the GitHub security advisory is published and states that exploitation of this path has been observed in the wild.
  • 2026-11-05: CVE-2026-116779 is published with CVSS v3.1 9.8.
  • 2026-11-06: the CVE is not in CISA KEV; no indicators or victim details are public.

The fix shipped on 2026-08-28, 61 days before the public advisory.
Figure 1: From the FreeSWITCH fix to the public advisory and CVE. Source: FreeSWITCH v1.11.3 release notes, commit 7015f6a7 and GHSA-mg63-h38w-8p9p, checked November 6, 2026

FAQ

Is my FreeSWITCH exposed if I only use SIP trunks?

Yes, if a trunk can deliver a MESSAGE to a profile that accepts chat. An authenticated trunk account is enough, and the stock default user can remove the authentication step entirely. Set enable-chat=false on profiles that do not need chat, or upgrade.

Does this affect hosted PBX and contact-center providers?

Any provider that runs FreeSWITCH 1.11.2 or earlier with inbound chat enabled is exposed. The fix is in the engine, so the provider must upgrade or disable chat on the profiles it exposes. Ask your provider which release it runs.

Is the attack unauthenticated?

In the stock conf/vanilla configuration, yes, because the default directory user has no password and no a1-hash. If that user is removed and allow-empty-password is false, the attacker needs one account on the profile, such as an extension or a trunk peer. That is still a low bar.

Was it exploited in the wild?

The advisory states that exploitation of this path has been observed. It does not give a date, a victim count or indicators. The CVE is not in CISA KEV as of 2026-11-06.

Does disable_system_api_commands=true fix it?

No. It removes the system API, but the route still reaches every other registered API, including module loading and call origination. Use it only as an additional narrowing measure, not as a replacement for the upgrade.

Does the fix break normal SIP chat?

No. It blocks only the api protocol unless the profile sets enable-chat-api-proto=true. Messages with no protocol selector, or with another chat protocol, route as before.

Find exposed SIP services with Ostorlab

Ostorlab's attack surface discovery inventories your internet-facing hosts and the services they expose. Use it to list the systems that answer on SIP ports, then check their FreeSWITCH release and profile configuration first.

References

  • FreeSWITCH security advisory GHSA-mg63-h38w-8p9p, published 2026-10-28, the root cause, affected configurations, workarounds and the exploitation statement: https://github.com/signalwire/freeswitch/security/advisories/GHSA-mg63-h38w-8p9p (checked November 6, 2026)
  • CVE-2026-116779 record, published 2026-11-05, CVSS v3.1 9.8 and the affected range: https://www.cve.org/CVERecord?id=CVE-2026-116779 (checked November 6, 2026)
  • FreeSWITCH commit 7015f6a73ab3d67fed5fbfc277ecfb88a74c2a86, "Add enable-chat-api-proto to gate the api chat proto", 2026-08-26: https://github.com/signalwire/freeswitch/commit/7015f6a73ab3d67fed5fbfc277ecfb88a74c2a86 (checked November 6, 2026)
  • FreeSWITCH pull request #3135, the fix and the vanilla profile changes: https://github.com/signalwire/freeswitch/pull/3135 (checked November 6, 2026)
  • FreeSWITCH v1.11.3 release notes, 2026-08-28, the new parameter and the To-user bound: https://github.com/signalwire/freeswitch/releases/tag/v1.11.3 (checked November 6, 2026)
  • FreeSWITCH sofia_presence.c at v1.11.2, the vulnerable To-user split: https://github.com/signalwire/freeswitch/blob/v1.11.2/src/mod/endpoints/mod_sofia/sofia_presence.c (checked November 6, 2026)
  • FreeSWITCH mod_dptools.c at v1.11.2, the api chat interface and api_chat_send(): https://github.com/signalwire/freeswitch/blob/v1.11.2/src/mod/applications/mod_dptools/mod_dptools.c (checked November 6, 2026)
  • FreeSWITCH project page, the statement that it powers carriers and contact centers: https://freeswitch.com/ (checked November 6, 2026)
  • Docker Hub, safarov/freeswitch, 1,366,696 pulls, a community image reach figure: https://hub.docker.com/r/safarov/freeswitch (checked November 6, 2026)
  • GitHub, signalwire/freeswitch, 5,283 stars, a repository reach figure: https://github.com/signalwire/freeswitch (checked November 6, 2026)
  • CISA Known Exploited Vulnerabilities catalog JSON feed, checked for CVE-2026-116779 on November 6, 2026 (not listed): https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json (checked November 6, 2026)