Direct answer: FreeSWITCH versions 1.11.2 and older have the flaw CVE-2026-116779 with a CVSS 3.1 score of 9.8. A SIP MESSAGE that a profile accepts can run FreeSWITCH commands, including operating system commands. Today, check the version, update to 1.11.3, then look for signs.

Attackers already use this flaw. CISA added it to KEV on 9 November 2026, with a patch deadline of 12 November 2026 for US federal agencies. The fix exists since 28 August 2026.

Main condition: This flaw affects every SIP profile that accepts inbound chat, and chat is on in the default settings. The inbound ACL does not stop this path. Limit: This article has no message format, header contents, payloads, or attack sequence.

We read the CVE-2026-116779 record, the advisory GHSA-mg63-h38w-8p9p, the FreeSWITCH 1.11.3 release notes, and the CVE record API on 10 November 2026. We tested 3 check scripts, a temporary mitigation, and an update from 1.11.2 to 1.11.3 in a podman container with the official debian:12 image. The container ran with no network. We did not send SIP messages and did not test an attack.

Problem: a SIP chat message that runs server commands

FreeSWITCH is open source telephony software from SignalWire. Businesses use it for office PBX, call centers, IVR, and SIP trunks. FusionPBX, a PBX panel that many Indonesian businesses use, runs FreeSWITCH behind the panel.

According to the CVE-2026-116779 record, CVE-2026-116779 is in the classes CWE-77 and CWE-78, which are command injection. GitHub is the CNA of this record, and the advisory comes from SignalWire. The score of 9.8 uses the vector AV:N, AC:L, PR:N, and UI:N, which means a network attack that is easy and needs no login.

Release 1.11.3 came out on 28 August 2026 and closes the flaw. The advisory GHSA-mg63-h38w-8p9p came out on 28 October, and the CVE record on 5 November 2026. So a server with no update since August is still open.

Timeline: 9 August release 1.11.2, 28 August release 1.11.3, 28 October advisory, 5 November CVE record, 6 November active attacks, 9 November KEV, 12 November deadline.
Teal dots: the fix. Red dots: the advisory, the CVE record, and the attacks. Amber dots: the last vulnerable release and the deadline. Source: GitHub SignalWire, CVE.org, and CISA.

On 10 November 2026, this flaw is in CISA KEV (catalog 2026.11.09). The CISA SSVC rating of 6 November gives Exploitation active, Automatable yes, and Technical Impact total. This means that attacks already occur, can run automatically, and have a full impact.

How it works: the api chat proto on the MESSAGE path

This section uses the official summary and the patch contents. Its aim is to help you select the steps, not to explain how to attack.

Summary from the official advisory

According to the advisory, the SIP MESSAGE handler in mod_sofia uses the first part of the destination address to select a chat proto. The mod_dptools module registers a proto with the name api, which runs the message contents as a FreeSWITCH APIAPIThe official door 2 systems use to exchange data, without anybody copying it by hand.Open the glossary command. So a party whose message the profile accepts can run commands with the privileges of the FreeSWITCH process.

The impact according to the advisory: operating system commands, a leak of configuration and credentials, and full control of calls. When mod_lua is loaded, and the default settings load it, Lua is a second path.

Default settings that open the path

  • Chat is on in every profile unless the profile has enable-chat=false. The vanilla conf keeps chat on in the internal and external profiles.
  • The inbound ACL does not cover MESSAGE. So a strict ACL does not remove a profile from the risk.
  • When message authentication is on, an attacker only needs 1 account in that profile: an extension, a handset, or a trunk.
  • The vanilla conf has 2 users with no password and no a1-hash: the default user and the Skinny example. Users like these pass authentication unless allow-empty-password is false.

What release 1.11.3 changes

According to the advisory and PR #3135, release 1.11.3 adds the profile parameter enable-chat-api-proto. This parameter is off unless you write it, so the api proto is closed in every profile by default. A message to that proto gets a 403 answer, and FreeSWITCH writes 1 log line with the source and the requested command.

Rama Digital analysis: that 403 log line only occurs on servers with 1.11.3 or newer. Older servers write no special trace for this path. Limit: we read the advisory, the patch code, and the strings in the built module, but we did not send a test message.

Exploitation status on 10 November 2026

SourceStatus
CISA KEVAdded on 9 November 2026, patch deadline for US federal agencies 12 November 2026
CVE-2026-116779 record (CVSS 3.1 from the CNA)Score 9.8, class Critical, CWE-77, CWE-78, and CWE-749
CISA SSVC (6 November 2026)Exploitation active, Automatable yes, Technical Impact total
SignalWire advisoryAttacks through this path are observed on the internet
VendorFlaw closed in 1.11.3, the latest version on 10 November 2026
4 cards: CVE-2026-116779 score 9.8, in CISA KEV since 9 November, impact of system commands and call control, and chat on by default, then a facts table and the safe version.
Red cards: the flaw, the score, and the attack status. Amber cards: the impact and the default settings. Table: the flaw facts on 10 November 2026. Teal box: the safe version. Source: the CVE-2026-116779 record, advisory GHSA-mg63-h38w-8p9p, CISA KEV, and Rama Digital analysis.

The score of 9.8, the KEV status, and active attacks put this flaw in the emergency class. A phone server keeps call recordings, voicemail, and trunk credentials. An attacker can also use your trunk for international calls, and you get the bill.

Who is most at risk

The risk depends on the version, the profiles that accept chat, and the accounts in the directory. This table helps you sort servers when you manage many PBX servers.

Server conditionRiskReason
Version 1.11.2 or older, SIP port open to the internetHighestAnyone on the internet can send a message to the profile
The default user or other users with no password still existHighestThat user passes message authentication
Multi-tenant FusionPBX or a PBX with many extensionsHighAn attacker only needs 1 account from any tenant
Profile open only on the LAN or VPNHighA compromised handset, softphone, or laptop can send a message
enable-chat=false in all profilesLowThe inbound chat path is closed, but the version stays vulnerable
Already on 1.11.3 with no enable-chat-api-protoLowStill look for signs when the old server was open since August
6 risk cards: an internet-facing SIP profile, users with no password, and old FusionPBX are red; LAN-only chat and mod_lua amber; already on 1.11.3 grey.
Red cards: the highest risk. Amber cards: check. Grey card: a low risk. Bottom box: the CISA KEV status. Source: the CVE-2026-116779 record, advisory GHSA-mg63-h38w-8p9p, CISA KEV, and Rama Digital analysis.

Rama Digital analysis: many business PBX servers open port 5060 to the internet, so that staff softphones can connect from outside the office. Servers like that are in the highest risk group. Close the SIP port for addresses that do not need access, but still update.

Phone servers often sit at the network edge, the same as VPNs and firewalls. Also check other edge devices, for example the SonicWall SSLVPN flaw CVE-2026-19868 and the Ivanti Connect Secure flaw CVE-2026-111357. Also patch server panels, for example the Control Web Panel flaw CVE-2026-77876, and the Linux kernel on the same server, for example the Linux kernel TCP flaw CVE-2026-98432.

Vulnerable and safe versions

All FreeSWITCH versions up to 1.11.2 are vulnerable to CVE-2026-116779. Version 1.11.3 closes the flaw and is the latest version on 10 November 2026.

FreeSWITCH versionStatus on 10 November 2026Update to
1.10.x and olderVulnerable to CVE-2026-116779 (9.8)1.11.3 today
1.11.0 to 1.11.2Vulnerable to CVE-2026-116779 (9.8)1.11.3 today
1.11.3Safe, the latest version on 10 November 2026Use this version
A table of FreeSWITCH versions: 1.11.2 and older vulnerable, 1.11.3 safe, the chat api mode off by default in 1.11.3, and Debian 11 no longer supported.
Red: vulnerable. Teal: safe. Amber: the operating system note. Bottom box: how to check the version and the contents of release 1.11.3. Source: the FreeSWITCH 1.11.3 release notes, advisory GHSA-mg63-h38w-8p9p, and Rama Digital analysis.

The version data comes from the advisory GHSA-mg63-h38w-8p9p and the FreeSWITCH 1.11.3 release notes. Release 1.11.3 no longer supports Debian 11 Bullseye. When your server still runs Debian 11, apply step 4 today, then plan an upgrade to Debian 12 before the FreeSWITCH update.

Before you start

  • SSH access with sudo to the FreeSWITCH server, or admin access to the FusionPBX panel.
  • An fs_cli that can connect to FreeSWITCH. The scripts use read commands only.
  • A recent backup of the conf folder, for example /etc/freeswitch or /usr/local/freeswitch/conf.
  • A slot in quiet hours. A profile restart and a FreeSWITCH restart drop active calls.
  • A list of all FreeSWITCH and FusionPBX servers, including standby servers and old servers.

Step 1: Check the FreeSWITCH version

Save this script as check-version-freeswitch.sh in the ~/cek folder. The script reads the version from fs_cli -x version, then from the freeswitch program file when the service is off. The script then compares the version with 1.11.3.

#!/usr/bin/env bash
# Check the FreeSWITCH version against CVE-2026-116779 (a SIP message runs commands, CVSS 3.1 9.8).
# Data: the CVE record and advisory GHSA-mg63-h38w-8p9p from SignalWire, release v1.11.3 on GitHub.
# Use on the server: bash check-version-freeswitch.sh
# Use for versions that you copy: bash check-version-freeswitch.sh 1.10.12 1.11.2 1.11.3
# The script only reads. The script changes nothing.

# All versions up to 1.11.2 are vulnerable. Release 1.11.3 (28 August 2026) closes the flaw.
SAFE=1.11.3

lower() { [ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ]; }

check() {
  local v="$1"
  if ! [[ $v =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    printf '%-8s UNKNOWN, compare it manually with the safe version table\n' "$v"
    return
  fi
  if lower "$v" "$SAFE"; then
    printf '%-8s VULNERABLE to CVE-2026-116779 (9.8) -> update to %s today\n' "$v" "$SAFE"
  else
    printf '%-8s SAFE for CVE-2026-116779\n' "$v"
  fi
}

if (( $# > 0 )); then
  for v in "$@"; do check "$v"; done
  exit 0
fi

v=""
FSCLI=""
for c in "$(command -v fs_cli 2>/dev/null)" /usr/local/freeswitch/bin/fs_cli /usr/bin/fs_cli; do
  [ -n "$c" ] && [ -x "$c" ] && FSCLI="$c" && break
done
FSBIN=""
for c in "$(command -v freeswitch 2>/dev/null)" /usr/local/freeswitch/bin/freeswitch /usr/bin/freeswitch; do
  [ -n "$c" ] && [ -x "$c" ] && FSBIN="$c" && break
done
if [ -n "$FSCLI" ]; then
  # Example output: FreeSWITCH Version 1.11.2-release~64bit (-release 64bit)
  v=$(timeout 10 "$FSCLI" -x version 2>/dev/null | grep -oE 'Version [0-9]+\.[0-9]+\.[0-9]+' | head -n1 | cut -d' ' -f2)
  [ -n "$v" ] && echo "Source: fs_cli (the running FreeSWITCH)"
fi
if [ -z "$v" ] && [ -n "$FSBIN" ]; then
  v=$(timeout 10 "$FSBIN" -version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
  [ -n "$v" ] && echo "Source: the freeswitch program file (the service may be stopped)"
fi
if [ -z "$v" ]; then
  echo "fs_cli and freeswitch are not found, or they cannot be read."
  echo "Try: /usr/local/freeswitch/bin/fs_cli -x version, or write the version as an argument."
  exit 1
fi
check "$v"
# On the server: read the version from the running FreeSWITCH
bash check-version-freeswitch.sh
# Check versions that you noted from other servers
bash check-version-freeswitch.sh 1.10.12 1.11.2 1.11.3
# Manual check without the script
fs_cli -x version
Terminal: fs_cli prints FreeSWITCH 1.11.2, the version check prints VULNERABLE. Manual mode: 1.10.12 and 1.11.2 vulnerable, 1.11.3 safe.
The test container uses the official debian:12 image and FreeSWITCH 1.11.2 from the official SignalWire source. The script only reads the version from fs_cli. Source: Rama Digital test in a podman debian:12 container with FreeSWITCH 1.11.2 and 1.11.3 from the official SignalWire source, with no network, 10 November 2026.

In FusionPBX, open Status > SIP Status. That page shows the running FreeSWITCH version.

Check the result: "VULNERABLE" means continue to steps 2 and 3 today. "SAFE" still means run step 5, because attackers can have used the server before the update.

Step 2: Check SIP profiles and users with no password

This script answers 3 questions: which profiles accept inbound chat, which users pass with no password, and which risk reduction steps are already on. The script also reads the profile status from sofia status.

Save this script as check-exposure-freeswitch.sh. The script only reads the conf files and runs fs_cli read commands. The script does not send SIP messages and does not change data.

#!/usr/bin/env bash
# Check the FreeSWITCH exposure to CVE-2026-116779 (a SIP message runs commands, CVSS 3.1 9.8).
# Data: the CVE record and advisory GHSA-mg63-h38w-8p9p from SignalWire, release v1.11.3 on GitHub.
# Use: bash check-exposure-freeswitch.sh [conf-folder]
# Default conf folder: /etc/freeswitch, then /usr/local/freeswitch/conf.
# The script reads conf files. If fs_cli exists, it also runs read-only fs_cli commands (version, sofia status, global_getvar).
# The script sends no SIP message. The script changes nothing.
# Note: this CVE affects SIP profiles that accept an inbound MESSAGE. A profile accepts chat unless it has enable-chat=false.

SAFE=1.11.3
CONF="${1:-}"
if [ -z "$CONF" ]; then
  for d in /etc/freeswitch /usr/local/freeswitch/conf; do [ -d "$d" ] && CONF="$d" && break; done
fi
if [ -z "$CONF" ] || [ ! -d "$CONF" ]; then
  echo "The FreeSWITCH conf folder is not found. Write the folder as an argument."
  exit 1
fi

# Replace control characters with a question mark (file names and conf text can come from other people).
safe() { LC_ALL=C tr '\001-\037\177-\237' '[?*]'; }
# Remove XML comments, also comments that cover many lines.
strip() {
  awk 'BEGIN{c=0} { s=$0; o=""; while (length(s)>0) {
    if (c) { i=index(s,"-->"); if (i==0) { s="" } else { s=substr(s,i+3); c=0 } }
    else   { i=index(s,"<!--"); if (i==0) { o=o s; s="" } else { o=o substr(s,1,i-1); s=substr(s,i+4); c=1 } } } print o }' "$@"
}
lower() { [ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ]; }

# Find fs_cli and the version.
FSCLI=""
for c in "$(command -v fs_cli 2>/dev/null)" /usr/local/freeswitch/bin/fs_cli /usr/bin/fs_cli; do
  [ -n "$c" ] && [ -x "$c" ] && FSCLI="$c" && break
done
FSBIN=""
for c in "$(command -v freeswitch 2>/dev/null)" /usr/local/freeswitch/bin/freeswitch /usr/bin/freeswitch; do
  [ -n "$c" ] && [ -x "$c" ] && FSBIN="$c" && break
done
VER=""; SOURCE=""
if [ -n "$FSCLI" ]; then
  VER=$(timeout 10 "$FSCLI" -x version 2>/dev/null | grep -oE 'Version [0-9]+\.[0-9]+\.[0-9]+' | head -n1 | cut -d' ' -f2)
  [ -n "$VER" ] && SOURCE="fs_cli (the running FreeSWITCH)"
fi
if [ -z "$VER" ] && [ -n "$FSBIN" ]; then
  VER=$(timeout 10 "$FSBIN" -version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
  [ -n "$VER" ] && SOURCE="the freeswitch program file"
fi

VULN=1
echo "Conf folder: $(printf '%s' "$CONF" | safe)"
if [ -z "$VER" ]; then
  echo "Version: not readable. The script assumes a vulnerable version."
elif lower "$VER" "$SAFE"; then
  echo "Version: $VER (VULNERABLE, the fix is in $SAFE). Source: $SOURCE"
else
  VULN=0
  echo "Version: $VER (has the fix). Source: $SOURCE"
fi
echo

true_val() { # same idea as switch_true in FreeSWITCH
  local v; v=$(printf '%s' "$1" | tr 'A-Z' 'a-z')
  case "$v" in true|yes|on|enabled|active|allow) return 0 ;; esac
  [[ $v =~ ^[0-9]+$ ]] && [ "$v" -gt 0 ] && return 0
  return 1
}

echo "1. SIP profiles and the chat path"
PROF_FILES=()
while IFS= read -r -d '' f; do PROF_FILES+=("$f"); done < <(find "$CONF/sip_profiles" "$CONF/autoload_configs/sofia.conf.xml" -type f -name '*.xml' -print0 2>/dev/null)
n_prof=0; n_chat=0; n_api=0; n_noauth=0; nama_chat=""
for f in "${PROF_FILES[@]}"; do
  while IFS='|' read -r nama chat api am dam; do
    [ -z "$nama" ] && continue
    n_prof=$((n_prof + 1))
    if [ "$chat" = "#empty#" ]; then chat_s="ON (default)"; chat_on=1
    elif true_val "$chat"; then chat_s="ON"; chat_on=1
    else chat_s="off"; chat_on=0; fi
    if [ "$api" = "#empty#" ]; then api_on=0; elif true_val "$api"; then api_on=1; else api_on=0; fi
    if [ "$dam" != "#empty#" ]; then
      true_val "$dam" && auth_on=0 || auth_on=1
    elif [ "$am" != "#empty#" ] && ! true_val "$am"; then auth_on=0
    else auth_on=1; fi
    if (( VULN == 0 )); then api_s="$( ((api_on)) && echo ON || echo off )"; else api_s="unknown in this version"; fi
    ((auth_on)) && auth_s="on" || auth_s="OFF"
    printf '   %-14s enable-chat: %-14s enable-chat-api-proto: %-26s auth-messages: %s\n' "$(printf '%s' "$nama" | safe)" "$chat_s" "$api_s" "$auth_s"
    if (( chat_on )); then
      n_chat=$((n_chat + 1)); nama_chat+="$nama "
      (( VULN == 0 && api_on )) && n_api=$((n_api + 1))
      (( auth_on == 0 )) && n_noauth=$((n_noauth + 1))
    fi
  done < <(strip "$f" | tr '\n' ' ' | awk -v RS='<' '
    function attr(s, k,   t) { if (match(s, k "=\"[^\"]*\"")) { t = substr(s, RSTART + length(k) + 2, RLENGTH - length(k) - 3); return t } return "" }
    /^profile[ \t]/ { in_p = 1; nm = attr($0, "name"); chat = api = am = dam = "#empty#"; next }
    /^\/profile/    { if (in_p) print nm "|" chat "|" api "|" am "|" dam; in_p = 0; next }
    in_p && /^param[ \t]/ { k = attr($0, "name"); v = attr($0, "value")
      if (k == "enable-chat") chat = v; else if (k == "enable-chat-api-proto") api = v
      else if (k == "auth-messages") am = v; else if (k == "disable-auth-messages") dam = v }')
done
(( n_prof == 0 )) && echo "   No profile in $(printf '%s' "$CONF" | safe)/sip_profiles. Check the conf folder."
echo "   Profiles with chat on: $n_chat of $n_prof"
if [ -n "$FSCLI" ]; then
  st=$(timeout 10 "$FSCLI" -x 'sofia status' 2>/dev/null | awk -F'\t' '{ for (i=1;i<=NF;i++) gsub(/^ +| +$/,"",$i) } $2=="profile" { printf "   running: %-14s %s\n", $1, $3 }')
  if [ -n "$st" ]; then printf '%s\n' "$st" | LC_ALL=C tr '\001-\011\013-\037\177-\237' '[?*]'; else echo "   fs_cli cannot read sofia status (the service is stopped or the fs_cli password is different)."; fi
fi

echo "2. Directory users with no password and no a1-hash"
n_user=0; daftar=""
while IFS= read -r -d '' f; do
  while IFS='|' read -r uid ada; do
    [ -z "$uid" ] && continue
    [ "$ada" = "none" ] || continue
    n_user=$((n_user + 1))
    (( n_user <= 10 )) && daftar+="   - user $(printf '%s' "$uid" | safe) in $(printf '%s' "${f#$CONF/}" | safe)"$'\n'
  done < <(strip "$f" | tr '\n' ' ' | awk -v RS='<' '
    function attr(s, k,   t) { if (match(s, k "=\"[^\"]*\"")) { t = substr(s, RSTART + length(k) + 2, RLENGTH - length(k) - 3); return t } return "" }
    /^user[ \t]/ { in_u = 1; id = attr($0, "id"); has = 0; if (attr($0, "type") == "pointer") has = 1; next }
    /^\/user/    { if (in_u) print id "|" (has ? "has" : "none"); in_u = 0; next }
    in_u && /^param[ \t]/ { k = attr($0, "name"); v = attr($0, "value"); if ((k == "password" || k == "a1-hash") && v != "") has = 1 }')
done < <(find "$CONF/directory" -type f -name '*.xml' -print0 2>/dev/null)
if (( n_user > 0 )); then
  echo "   $n_user users can sign in with no password (unless allow-empty-password is false):"
  printf '%s' "$daftar"
else
  echo "   0 users."
fi
sandi_bawaan=$(strip "$CONF/vars.xml" 2>/dev/null | grep -oE 'default_password=[^"]*' | head -n1 | cut -d= -f2)
if [ "$sandi_bawaan" = "1234" ]; then
  n_def=$(grep -rhE 'name="password" value="\$\$\{default_password\}"' "$CONF/directory" 2>/dev/null | wc -l)
  echo "   The default password 1234 is still set in vars.xml and $n_def users use it. Change it."
fi

echo "3. Other risk reducers"
sysoff=""
if [ -n "$FSCLI" ]; then
  sysoff=$(timeout 10 "$FSCLI" -x 'global_getvar disable_system_api_commands' 2>/dev/null | head -n1)
fi
if [ -n "$sysoff" ] && true_val "$sysoff"; then
  echo "   disable_system_api_commands: true (the system commands are off). Other API commands stay reachable."
elif strip "$CONF/vars.xml" 2>/dev/null | grep -qE 'disable_system_api_commands=true'; then
  echo "   disable_system_api_commands=true is in vars.xml (it applies after you restart FreeSWITCH)."
else
  echo "   disable_system_api_commands: not set. The system commands are still available."
fi
if strip "$CONF/autoload_configs/modules.conf.xml" 2>/dev/null | grep -qE '<load[[:space:]]+module="mod_lua"'; then
  echo "   mod_lua: in the load list of modules.conf.xml (a second path through Lua). Remove that line if you do not use it."
else
  echo "   mod_lua: not in the load list of modules.conf.xml."
fi

echo
if (( VULN )); then
  if (( n_chat > 0 )); then
    echo "Result: HIGH EXPOSURE. The version is vulnerable and $n_chat profiles accept an inbound MESSAGE (${nama_chat% })."
    echo "Update to $SAFE. Until you update: write enable-chat=false in each profile that needs no inbound SIP chat."
  else
    echo "Result: LOW EXPOSURE. The version is vulnerable, but no profile accepts chat. Update to $SAFE anyway."
  fi
else
  if (( n_api > 0 )); then
    echo "Result: HIGH EXPOSURE. The version has the fix, but $n_api profiles turn enable-chat-api-proto on."
    echo "Remove that parameter unless a trusted peer needs it."
  else
    echo "Result: SAFE. The version has the fix and no profile opens the api proto."
  fi
fi
# Default conf folder: /etc/freeswitch, then /usr/local/freeswitch/conf
bash check-exposure-freeswitch.sh
# Another conf folder, for example a custom install
bash check-exposure-freeswitch.sh /opt/freeswitch/conf
# Check on the server: the open SIP ports (5060 and 5080 in the default settings)
sudo ss -lunp | grep -E ':(5060|5080)\b'
Terminal: the exposure check prints a vulnerable version, 4 SIP profiles with chat on, 2 users with no password, and the result HIGH EXPOSURE.
The conf in the container is the vanilla conf from the official source, with no change. The container runs with no network, so the external IP uses the local container IP. Source: Rama Digital test in a podman debian:12 container with FreeSWITCH 1.11.2 and 1.11.3 from the official SignalWire source, with no network, 10 November 2026.

In our test with the vanilla conf, 4 of 4 profiles accept inbound chat, and 2 users pass with no password: default and the Skinny example. The result is HIGH EXPOSURE. The script also found the default password 1234 in vars.xml, which 21 example users use.

The vanilla conf also puts mod_lua in the load list. According to the advisory, Lua is a second path. Our test build does not install mod_lua, so the script only reads that load list.

In FusionPBX, the script still reads the profile XML files that the panel makes. But change the settings through the panel, because the panel writes those files again.

Check the result: "HIGH EXPOSURE" means update within 1 hour, or apply step 4 now. Remove each user with no password or give it a password, because that user also opens other paths.

Step 3: Back up, then update to 1.11.3

Only the update closes the flaw. Make a conf backup first, then update in the same way as your first install.

# Install from source. Run it in quiet hours, because a restart drops active calls.
# 1. Back up the conf folder, then note the configure options of the old build
sudo tar czf /root/conf-freeswitch-before-update.tar.gz -C /usr/local/freeswitch conf
grep -m1 '\$ ./configure' /usr/src/freeswitch-1.11.2/config.log
# 2. Download the 1.11.3 source, then build with the same modules.conf and configure options
cd /usr/src
sudo curl -fL -o freeswitch-1.11.3.tar.gz https://github.com/signalwire/freeswitch/archive/refs/tags/v1.11.3.tar.gz
sudo tar xzf freeswitch-1.11.3.tar.gz && cd freeswitch-1.11.3
sudo cp ../freeswitch-1.11.2/modules.conf .
sudo ./bootstrap.sh -j && sudo ./configure --disable-libvpx --disable-libyuv && sudo make -j4
# 3. Stop FreeSWITCH, install, then start it again
fs_cli -x 'shutdown'
sudo make install
sudo systemctl start freeswitch
# 4. Check the version again
bash ~/cek/check-version-freeswitch.sh
Terminal: the 1.11.3 build from source completes, make install completes, 4 profiles run again, fs_cli prints 1.11.3, and the script prints SAFE.
The build uses the same modules.conf and configure options as the 1.11.2 build. The old conf folder stays in use. Source: Rama Digital test in a podman debian:12 container with FreeSWITCH 1.11.2 and 1.11.3 from the official SignalWire source, with no network, 10 November 2026.
  1. Make a backup of the conf folder, then move the file off the server.
  2. Note the configure options from the first line of config.log in the old source folder. Use the same options in the new build.
  3. Build 1.11.3, stop FreeSWITCH in quiet hours, run make install, then start FreeSWITCH again.
  4. Run check-version-freeswitch.sh again. The correct result: "SAFE for CVE-2026-116779".
  5. Make 1 inbound test call and 1 outbound test call, then do it again on each server.

In our test, the 1.11.3 build from source completed with the same modules.conf. After make install, the old conf stayed in use, 4 profiles ran again, and fs_cli printed version 1.11.3. The log string that rejects the api proto is in the built mod_sofia.so.

For an install from the SignalWire Debian packages, make sure that the candidate version is 1.11.3, then update the FreeSWITCH packages. That repository needs a SignalWire access token, so we did not test this path.

# Install from the SignalWire Debian packages (we did not test this): check the candidate version first
sudo apt-get update
apt-cache policy freeswitch | head -n 3
# Update only the FreeSWITCH packages that are already installed
sudo apt-get install --only-upgrade $(dpkg-query -W -f='${binary:Package}\n' 'freeswitch*')
sudo systemctl restart freeswitch

For FusionPBX, check how FreeSWITCH is installed on your server: from source or from packages. Then use the same path. Do not write enable-chat-api-proto=true after the update, unless a trusted peer really needs that proto.

A 5-row ladder: check the version, check SIP profiles and users with no password, update to 1.11.3, enable-chat=false labelled temporary only, then look for signs.
(1) to (3): the fix order. (4): a temporary layer. The amber label reminds you that only the update closes the flaw in all profiles. Source: advisory GHSA-mg63-h38w-8p9p, the 1.11.3 release notes, and Rama Digital analysis.

Check the result: the version script prints "SAFE" on each server, the exposure script prints "SAFE", and the inbound and outbound test calls work.

Step 4: Turn off inbound SIP chat when the update must wait

This step is only a temporary layer. Use this step when you cannot update today, for example because the server still runs Debian 11 or needs a test on a standby server first.

According to the advisory, enable-chat=false stops the message before the proto selector is read. The side effect is that all inbound SIP chat on that profile stops. Calls use another path, so in our analysis calls are not affected.

# When you cannot update today: turn off inbound SIP chat in every profile.
# Replace /usr/local/freeswitch/conf with your conf folder, for example /etc/freeswitch.
cd /usr/local/freeswitch/conf/sip_profiles
for f in *.xml; do sudo cp -p "$f" "$f.bak-cve"; done
sudo sed -i 's|<settings>|<settings>\n    <param name="enable-chat" value="false"/>|' *.xml
grep -n 'enable-chat' *.xml
# Reload the conf, then restart each profile. A profile restart drops the calls on that profile.
fs_cli -x 'reloadxml'
for p in $(fs_cli -x 'sofia status' | awk '$2=="profile" {print $1}'); do fs_cli -x "sofia profile $p restart"; done
fs_cli -x 'sofia status' | grep -c RUNNING
bash ~/cek/check-exposure-freeswitch.sh
Terminal: a backup of 4 profile files, enable-chat=false in 4 profiles, reloadxml, a profile restart, 4 profiles running, then the exposure check prints LOW EXPOSURE.
The version stays 1.11.2, so this step is not a fix. This step only closes the inbound chat path in the profiles that you change. Source: Rama Digital test in a podman debian:12 container with FreeSWITCH 1.11.2 and 1.11.3 from the official SignalWire source, with no network, 10 November 2026.

In our test, 4 profiles got enable-chat=false, then all 4 profiles ran again after the restart. The exposure script changed from HIGH EXPOSURE to LOW EXPOSURE, but the version stayed 1.11.2.

In FusionPBX, open Advanced > SIP Profiles, open each profile, then add the setting enable-chat with the value false. Save, then restart the profile from Status > SIP Status. We did not test FusionPBX.

2 other steps from the advisory do not close this path. auth-messages and the removal of users with no password only limit the senders to registered accounts. disable_system_api_commands=true only turns off the system command, while other API commands stay reachable.

Rama Digital analysis: enable-chat=false only reduces the ways in. The flaw stays in the code until version 1.11.3. After the update, you can turn chat on again when your team uses it.

Check the result: grep finds enable-chat with the value false in each profile, all profiles show RUNNING, and the exposure script prints LOW EXPOSURE.

Step 5: Look for signs of abuse

The last vulnerable release came out on 9 August 2026, and attacks are already observed. Look for signs on every server, including servers that you updated today.

Save this script as check-signs-freeswitch.sh. The script reads 5 items: 403 log lines that reject the api proto, FreeSWITCH child processes, changed conf files, directory users in recently changed files, and the crontab and authorized_keys of the service user.

Run it with sudo, because the logs and the crontab of the service user are usually readable only by root. The script does not run FreeSWITCH commands. The script replaces control characters in file names and log lines with a question mark, so that text from an attacker cannot change your terminal.

#!/usr/bin/env bash
# Look for signs of CVE-2026-116779 (FreeSWITCH) use in the log, the processes, the conf folder, and the service account.
# Use: sudo bash check-signs-freeswitch.sh [log-folder] [conf-folder] [days] [service-user]
# Defaults: log /var/log/freeswitch, then /usr/local/freeswitch/log. Conf /etc/freeswitch, then /usr/local/freeswitch/conf.
# Default days: 30. Default service users: the owner of the freeswitch process, and the user "freeswitch" if it exists.
# The script only reads files and the process list. The script runs no FreeSWITCH command.
# A server below 1.11.3 writes no rejection log. An empty result there is not proof of safety.
# File names and log lines can come from an attacker, so the script replaces control characters with question marks.

LOGDIR="${1:-}"; CONF="${2:-}"; DAYS="${3:-30}"; USERS_ARG="${4:-}"
if [ -z "$LOGDIR" ]; then for d in /var/log/freeswitch /usr/local/freeswitch/log; do [ -d "$d" ] && LOGDIR="$d" && break; done; fi
if [ -z "$CONF" ]; then for d in /etc/freeswitch /usr/local/freeswitch/conf; do [ -d "$d" ] && CONF="$d" && break; done; fi
[[ $DAYS =~ ^[0-9]+$ ]] || DAYS=30
safe() { LC_ALL=C tr '\001-\037\177-\237' '[?*]'; }
strip() {
  awk 'BEGIN{c=0} { s=$0; o=""; while (length(s)>0) {
    if (c) { i=index(s,"-->"); if (i==0) { s="" } else { s=substr(s,i+3); c=0 } }
    else   { i=index(s,"<!--"); if (i==0) { o=o s; s="" } else { o=o substr(s,1,i-1); s=substr(s,i+4); c=1 } } } print o }' "$@"
}
signs=0

echo "1. Log: MESSAGE to the api proto rejected with 403 (only on server 1.11.3 or newer)"
LOGS=()
if [ -n "$LOGDIR" ] && [ -d "$LOGDIR" ]; then
  for f in "$LOGDIR"/freeswitch.log*; do [ -f "$f" ] && LOGS+=("$f"); done
fi
if (( ${#LOGS[@]} == 0 )); then
  echo "   The log is not found. Write the log folder as the first argument."
else
  isi=$(zcat -f "${LOGS[@]}" 2>/dev/null | LC_ALL=C tr '\001-\011\013-\037\177-\237' '[?*]' | grep -F "rejected a MESSAGE from" | grep -F "'api' chat proto")
  n=$(printf '%s' "$isi" | grep -c .)
  echo "   Rejection lines: $n"
  if (( n > 0 )); then
    printf '%s\n' "$isi" | sed -E "s/^([0-9-]+ [0-9:]+)[^[]*\[[A-Z]+\].*Profile \[([^]]*)\] rejected a MESSAGE from ([^ ]*) \(([^)]*)\) addressed.*/   \1  profile \2  from \3 (\4)/" | cut -c1-150 | tail -n 10
    echo "   Top 5 sources:"
    printf '%s\n' "$isi" | sed -E 's/.*rejected a MESSAGE from [^ ]* \(([^):]*)[:)].*/\1/' | sort | uniq -c | sort -rn | head -n 5 | awk '{printf "   IP %s: %d times\n", $2, $1}'
    echo "   The requested command is in the log file. Read it there. Do not copy it to a terminal."
    signs=$((signs + 1))
  fi
fi

echo "2. Child processes of the freeswitch process"
PID=$(pgrep -x freeswitch 2>/dev/null | head -n1)
if [ -z "$PID" ]; then
  echo "   The freeswitch process is not running."
else
  anak=$(ps -eo pid=,ppid=,etime=,args= 2>/dev/null | awk -v root="$PID" '
    { pid=$1; ppid=$2; et=$3; $1=$2=$3=""; sub(/^ +/,""); par[pid]=ppid; cmd[pid]=$0; ela[pid]=et }
    END { for (p in par) { q=p; while (q in par) { if (par[q]==root) { printf "%s %s %s\n", p, ela[p], substr(cmd[p],1,100); break } q=par[q] } } }' | safe)
  if [ -n "$anak" ]; then
    echo "   CHECK: FreeSWITCH normally has no long-lived child process:"
    printf '%s\n' "$anak" | sed 's/^/   PID /'
    signs=$((signs + 1))
  else
    echo "   0 child processes."
  fi
fi

echo "3. Conf files that changed in the last $DAYS days"
if [ -z "$CONF" ] || [ ! -d "$CONF" ]; then
  echo "   The conf folder is not found. Write the conf folder as the second argument."
else
  baru=$(find "$CONF" -type f -mtime -"$DAYS" -printf '%TY-%Tm-%Td %TH:%TM  %s B  %p\0' 2>/dev/null | safe | tr '\0' '\n' | sort | tail -n 15)
  if [ -n "$baru" ]; then
    echo "   CHECK: compare with the changes that your team made:"
    printf '%s\n' "$baru" | sed 's/^/   /'
    signs=$((signs + 1))
  else
    echo "   0 files."
  fi
fi

echo "4. Directory users in files that changed recently"
if [ -n "$CONF" ] && [ -d "$CONF/directory" ]; then
  n_u=0; dft=""
  while IFS= read -r -d '' f; do
    ids=$(strip "$f" | tr '\n' ' ' | awk -v RS='<' '/^user[ \t]/ { if (match($0, "id=\"[^\"]*\"")) print substr($0, RSTART+4, RLENGTH-5) }' | safe | tr '\n' ' ')
    [ -z "$ids" ] && continue
    n_u=$((n_u + 1)); dft+="   $(stat -c '%y' "$f" | cut -c1-16)  user: ${ids% }  ($(printf '%s' "${f#$CONF/}" | safe))"$'\n'
  done < <(find "$CONF/directory" -type f -name '*.xml' -mtime -"$DAYS" -print0 2>/dev/null)
  if (( n_u > 0 )); then
    echo "   CHECK: a user that you do not know can be the account for this path:"
    printf '%s' "$dft"
    signs=$((signs + 1))
  else
    echo "   0 new directory files."
  fi
else
  echo "   The directory folder does not exist."
fi

echo "5. Crontab and authorized_keys of the service user"
USERS=()
if [ -n "$USERS_ARG" ]; then USERS=("$USERS_ARG"); else
  [ -n "$PID" ] && USERS+=("$(ps -o user= -p "$PID" 2>/dev/null | tr -d ' ')")
  getent passwd freeswitch >/dev/null 2>&1 && USERS+=(freeswitch)
fi
USERS=($(printf '%s\n' "${USERS[@]}" | sort -u))
if (( ${#USERS[@]} == 0 )); then
  echo "   The service user is not found. Write the user name as the fourth argument."
fi
for u in "${USERS[@]}"; do
  [[ $u =~ ^[a-z_][a-z0-9_.-]*$ ]] || continue
  found=0
  for cf in "/var/spool/cron/crontabs/$u" "/var/spool/cron/$u"; do
    if [ -f "$cf" ]; then
      found=1
      active=$(grep -cvE '^[[:space:]]*(#|$)' "$cf")
      echo "   CHECK: user $u has a crontab ($(stat -c '%y' "$cf" | cut -c1-16), $active active lines)."
      grep -vE '^[[:space:]]*(#|$)' "$cf" | head -n 5 | cut -c1-100 | safe | sed 's/^/     /'
      signs=$((signs + 1))
    fi
  done
  home=$(getent passwd "$u" 2>/dev/null | cut -d: -f6)
  if [ -n "$home" ] && [ -f "$home/.ssh/authorized_keys" ]; then
    found=1
    n_k=$(grep -cvE '^[[:space:]]*(#|$)' "$home/.ssh/authorized_keys")
    echo "   CHECK: user $u has authorized_keys ($n_k keys, changed $(stat -c '%y' "$home/.ssh/authorized_keys" | cut -c1-16))."
    signs=$((signs + 1))
  fi
  (( found == 0 )) && echo "   User $u: no crontab and no authorized_keys."
done

echo
if (( signs > 0 )); then
  if [ "$signs" -eq 1 ]; then w=SIGN; else w=SIGNS; fi
  echo "Result: $signs $w to check. If the files or lines are not from your team, go to incident response."
else
  echo "Result: no SIGNS from this check. An attacker can delete logs, so update and monitor anyway."
fi
# Logs, child processes, conf changed in 30 days, new directory users, crontab, and authorized_keys
sudo bash check-signs-freeswitch.sh
# Other log and conf folders, range of 75 days (since the 1.11.2 release on 9 August 2026)
sudo bash check-signs-freeswitch.sh /var/log/freeswitch /etc/freeswitch 75
# Check unknown outbound calls in the CSV CDR (when mod_cdr_csv is on)
sudo tail -n 50 /var/log/freeswitch/cdr-csv/Master.csv
Terminal: the signs check reads the log, freeswitch child processes, changed conf, directory users, crontab, and authorized_keys, then prints the result.
The test server was never attacked. The profile files in check 3 are our own changes from step 4. The container clock uses UTC. Source: Rama Digital test in a podman debian:12 container with FreeSWITCH 1.11.2 and 1.11.3 from the official SignalWire source, with no network, 10 November 2026.
2 columns of signs: in logs and SIP traffic, 403 lines, MESSAGE from unknown IPs, and call spikes; on the server, child processes, changed conf, new users, and crontab.
Amber column: signs in logs and SIP traffic. Red column: signs on the server. Bottom box: the steps when you find even 1 sign. Source: the CVE-2026-116779 record, advisory GHSA-mg63-h38w-8p9p, the 1.11.3 release notes, and Rama Digital analysis.

In our test, the server was never attacked. The script still found changed profile files, which are our own changes from step 4. Compare each file that appears with the change records of your team.

The 403 log line exists only after the update to 1.11.3. On an old server, look for other traces: sh or bash processes under the freeswitch process, new users in the directory, and unknown international calls in the CDR.

Check the result: the script prints "no SIGNS from this check", or each sign matches a change of your team. An attacker can delete logs, so no signs does not prove that the server is safe.

When you find even 1 sign, keep the logs, the conf files, and the process list as evidence. Change the passwords of all extensions, the SIP trunk passwords, and the fs_cli password in event_socket.conf.xml. Then update to 1.11.3 and ask your trunk provider to check the bill.

A phone server keeps call recordings, voicemail, and customer numbers. When personal data leaks, Indonesian Law 27/2022 on Personal Data Protection requires a written notice no later than 3 times 24 hours after the leak.

Example: 1 morning of response for 3 phone servers

This simulation uses dummy data. The server names, times, and results are not customer data.

Time (WIB)Dummy serverChecksResultAction
08.00callcenter-a (FusionPBX, FreeSWITCH 1.10.12, port 5060 open to the internet)Steps 1, 2, and 5HIGH EXPOSURE, 1 unknown sh process under freeswitchIncident response, then update
08.30pbx-office-b (1.11.2 from source, LAN only)Steps 1 and 2HIGH EXPOSURE, default user with no passwordStep 4 this morning, update to 1.11.3 at 12.00
09.00trunk-c (1.11.3, Debian 12)Steps 1, 2, and 5SAFE, 0 SIGNSKeep the output as evidence

The lesson from the simulation: a server open to the internet needs step 5 first, not only the update. The update closes the flaw, but it does not remove accounts, SSH keys, or crontab entries that are already in place.

Who installs the patch

The patch responsibility is different for each service type, so this table helps you know who acts today.

Service typePatch ownerWhat you do
FreeSWITCH or FusionPBX on your own VPSYour teamSteps 1 to 5
FusionPBX that a vendor or integrator managesAs in the maintenance contractAsk for the FreeSWITCH version today, then ask for the output of steps 1 and 5 as evidence
Cloud PBX from a providerThe providerAsk whether the service uses FreeSWITCH and when the patch is in place
On-premise PBX device based on FreeSWITCHThe device vendorCheck for new firmware, then close the SIP port from the internet until the firmware is in place

Rama Digital recommendation: put FreeSWITCH in the monthly patch schedule, the same as the operating system. Patch critical flaws within 24 hours, and flaws in CISA KEV on the same day.

Checklist for today

  1. Make a list of all FreeSWITCH and FusionPBX servers. Owner: IT team. Evidence: a list of servers and versions.
  2. Check the version with step 1. Owner: PBX admin. Evidence: script output.
  3. Check profiles and users with no password with step 2. Owner: PBX admin. Evidence: script output.
  4. Make a conf backup, then update to 1.11.3. Owner: PBX admin. Evidence: version script output after the update.
  5. When the update must wait, apply step 4. Owner: PBX admin. Evidence: exposure script output.
  6. Look for signs with step 5. Owner: PBX admin and security team. Evidence: script output and 30 days of CDR.
  7. Close the SIP port from the internet for addresses that do not need access.

Questions and answers

Do attackers already use this flaw?

Yes. The CVE record and the SignalWire advisory state that attacks through this path are observed. CISA added this flaw to KEV on 9 November 2026.

My server is open only on the LAN. Am I safe?

No. Each account or device that can send a message to the profile can use this path. The inbound ACL also does not cover MESSAGE, so still update to 1.11.3.

Does disable_system_api_commands close this flaw?

No. That setting only turns off the system command. According to the advisory, other API commands stay reachable, including module loads and new calls.

I use FusionPBX. Am I affected?

Yes, when the FreeSWITCH under the panel is still 1.11.2 or older. Check the version in Status > SIP Status, then update FreeSWITCH with the install path of your server.

Does enable-chat=false drop calls?

Not for new calls. That setting only turns off inbound SIP chat. But the profile restart that applies the setting drops the active calls on that profile.

How do I know that someone abused this flaw?

Run step 5. Check freeswitch child processes, changed conf and users, crontab, SSH keys, and unknown calls in the CDR. After the update, also check for 403 log lines that reject the api proto.

Next step

The limits that still apply: enable-chat=false is not a fix, and an update does not remove accounts or keys that are already in place. When your server shows a sign in step 5, or you need help with an audit and recovery, use Vibe Code Rescue. For 1 urgent issue today, book a 30-minute session.

Sources