# FreeSWITCH SIP MESSAGE api chat proto command execution (CVE-2026-116779, GHSA-mg63-h38w-8p9p)

A defensive threat-hunting and exposure-check pack, built 2026-11-09 (Europe/London, GMT). It includes no exploit
payloads and none should be added. The checkers never send SIP, never connect to anything and never write files.

The KQL in `kql/` and the converted Sigma in `sigma/converted/` have NOT been run against a live Sentinel or
Defender tenant. Only hand review and offline Sigma conversion were done. Check any field marked `[UNVERIFIED]`
against your own data first.

## Act now

- **Patch to FreeSWITCH 1.11.3 now**, whatever your sector. The CISA KEV due date of 2026-11-12 applies only to US
  federal civilian agencies under BOD 26-04. It does not make anyone else's deadline later, and exploitation in
  the wild has already been observed.
- 1.11.3 has been available since 2026-08-28, two months before the advisory. If any host is still below it,
  treat that as a patching gap.
- Find every FreeSWITCH install, including hosted PBX and VoIP platforms built on FreeSWITCH. Ask those vendors
  directly whether they have shipped the fix.
- If a host shows signs of compromise: isolate it, keep its logs and SIP captures, and **rotate every SIP, trunk,
  ESL and directory credential** plus any other secret in its config. Then rebuild or patch it and check your
  carrier bills for toll fraud.

## Verified facts (primary sources, checked 2026-11-09 17:20 GMT)

| Field | Value | Source |
|---|---|---|
| Identifiers | CVE-2026-116779, GHSA-mg63-h38w-8p9p. CVE reserved 2026-10-26 and published 2026-11-05 19:30 GMT by GitHub (CNA, `GitHub_M`) | cve.org record (cveawg API) |
| Advisory | "Inbound SIP MESSAGE can select the api chat proto and run FreeSWITCH API commands", published by the FreeSWITCH maintainers on 2026-10-28 16:46 GMT. The API shows updated_at equal to published_at, so the text has not been edited since | GitHub repository advisory (API) |
| Severity | CVSS 3.1 9.8 Critical, `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H`. CWE-77, CWE-78 and CWE-749. This is the single score from the maintainers' advisory, which the GitHub CNA carried into the CVE. No reviewed entry exists in GitHub's global advisory database (the API returns 404) | advisory, cve.org, NVD |
| NVD | Status "Undergoing Analysis" (last modified 2026-11-09 17:17 GMT). NVD has not scored it; only the CNA's 9.8 is shown, as Secondary | NVD API 2.0 |
| CISA SSVC (ADP) | Exploitation active, automatable yes, technical impact total (2026-11-09 16:50 GMT) | cve.org ADP container, NVD |
| Affected / fixed | Affected `<= 1.11.2` (CVE: `< 1.11.3`), fixed `>= 1.11.3` | advisory, cve.org |
| Root cause | `sofia_presence_handle_sip_i_message()` splits the To user of an inbound MESSAGE on `+` and treats the left part as a chat proto. `mod_dptools` registers an `api` chat interface that passes its address to `switch_api_execute()` | advisory, v1.11.3 source |
| Impact | Command execution with the privileges of the FreeSWITCH process: arbitrary OS commands, disclosure of configuration and credentials, and full call control. Where `mod_lua` is loaded (stock config loads it), inline Lua with the full standard library is a second sink | advisory, vanilla `modules.conf.xml` |
| Exploitation | The advisory says: "Exploitation of this path has been observed in the wild." The CVE and NVD descriptions repeat this | advisory, cve.org, NVD |
| Affected configs | Any SIP profile that accepts inbound MESSAGE. Chat is on unless `enable-chat=false` (the sofia.c default sets it on), and stock `conf/vanilla` leaves it on for both internal and external. The inbound ACL does not cover MESSAGE | advisory, sofia.c |
| Prerequisites | One accepted MESSAGE. Where MESSAGE authentication is enforced, any one account on the profile is enough. A vanilla `directory/default/default.xml` defines `<user id="default">` with no `password` and no `a1-hash`. Such a user authenticates unless `allow-empty-password` is false for it, so no account is needed | advisory, vanilla directory, sofia_reg.c |
| Passwordless vanilla users | Two: `default` and the Skinny example (`skinny-example.xml`, user `SEP001120AABBCC`) | advisory, vanilla directory at v1.11.3 |
| MESSAGE auth | "MESSAGE authentication is enabled by default from version 1.10.7 onward" (`auth-messages`, profile param) | advisory, sofia.c |
| Fix | 1.11.3 adds the per-profile param `enable-chat-api-proto`, which is off unless set. A MESSAGE that selects `api` on a profile without it gets a 403 and is logged with the source and the requested command. The match is case-insensitive. The param ships commented out in the four vanilla SIP profiles and in the `sofia.conf.xml` sample | advisory, commit 7015f6a (PR #3135) |
| Other 1.11.3 changes | `[mod_sofia] Bound the To-user split in the inbound MESSAGE handler` (commit 098018d, PR #3134). `[mod_verto] Add enable-chat-api-proto to gate the api chat proto` (commit f390069, PR #3136): verto client messages could also select the `api` proto, bypassing the per-user `verto.fsapi` permission. Release notes call 1.11.3 "an important release containing critical security fixes" | release notes, commits |
| Release dates | v1.11.3 tagged 2026-08-28 20:49 GMT and published 22:27 GMT. The fix commit is dated 2026-08-26. From 2026-08-09, master reported `1.11.3-dev` before the fix landed, so a `1.11.3-dev` build is not proof of the fix | GitHub release, compare v1.11.2...v1.11.3 |
| 403 log line | `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.` (WARNING) | commit 7015f6a |
| Workarounds | `enable-chat=false` on every profile that does not need inbound chat: this stops the message before the proto is parsed. `auth-messages` plus removing passwordless users only reduces the risk to authenticated execution. `disable_system_api_commands=true` removes the `system` API but leaves the route open to every other API, including module loading and origination. The advisory treats none of these as a fix | advisory |
| `disable_system_api_commands` | A global variable read by `mod_commands` at load time (`switch_core_get_variable`). It is set with `<X-PRE-PROCESS cmd="set" data="disable_system_api_commands=true"/>` (for example in `vars.xml`). It is not a SIP profile param | mod_commands.c v1.11.3 |
| KEV | Listed. Catalog version 2026.11.09 (released 2026-11-09 16:16 GMT, 1772 entries). The entry: vendorProject "SignalWire", product "FreeSwitch", name "SignalWire FreeSwitch Code Injection Vulnerability", dateAdded 2026-11-09, dueDate 2026-11-12, knownRansomwareCampaignUse "Unknown", forensicTriage "Yes", CWEs 77/78/749. The required action points to BOD 26-04 and CISA's "Forensics Triage Requirements" | CISA KEV JSON feed |
| Credit | Reported by security researchers | advisory |

Primary sources:
- https://github.com/signalwire/freeswitch/security/advisories/GHSA-mg63-h38w-8p9p
- https://github.com/signalwire/freeswitch/releases/tag/v1.11.3
- https://github.com/signalwire/freeswitch/commit/7015f6a73ab3d67fed5fbfc277ecfb88a74c2a86 (PR #3135)
- https://github.com/signalwire/freeswitch/commit/098018df3dc2766f08c9d25bebc69014b6e4bed5 (PR #3134)
- https://github.com/signalwire/freeswitch/commit/f3900693080eba3cd69864c5cf615955356ade4f (PR #3136, mod_verto)
- https://www.cve.org/CVERecord?id=CVE-2026-116779
- https://nvd.nist.gov/vuln/detail/CVE-2026-116779
- https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-116779 (feed: known_exploited_vulnerabilities.json)
- https://www.cisa.gov/news-events/directives/bod-26-04-prioritizing-security-updates-based-risk
- https://attack.mitre.org/techniques/T1190/ , T1059/004, T1505, T1071 (MITRE ATT&CK tags used in Sigma)

## Corrections to the initial report

| Teammate claim | Finding |
|---|---|
| CVSS 9.8, rated by "the vendor and GitHub both" | 9.8 is correct, but it is one score, not two. The maintainers published it in their repository advisory and the GitHub CNA carried it into the CVE. GitHub's reviewed global advisory database has no entry. NVD has not scored it yet (Undergoing Analysis) |
| Disclosed 28 Oct, exploitation reported then | Confirmed. The advisory was published 2026-10-28 16:46 GMT, states that exploitation has been observed in the wild, and has not been edited since. The CVE itself was only published on 5 Nov |
| (missing) Fix date | The fix shipped in 1.11.3 on 2026-08-28, two months before the advisory |
| KEV 9 Nov, due 12 Nov, forensic triage | Confirmed: catalog 2026.11.09, forensicTriage "Yes", ransomware use "Unknown". The due date binds only US federal agencies (BOD 26-04) |
| 1.11.2 and earlier | Confirmed. Builds labelled `1.11.3-dev` from master between 9 and 26 Aug do not have the fix |
| `api+<command>` To user runs API commands, OS commands as the freeswitch user | Confirmed, "with the privileges of the FreeSWITCH process". That may be root if the service runs as root. The proto match is case-insensitive (`API+` works too) |
| mod_lua inline Lua is a second route | Confirmed. The advisory adds that the stock config loads mod_lua |
| Inbound ACL doesn't cover MESSAGE | Confirmed |
| One registered account is enough | Confirmed where MESSAGE auth is enforced. The advisory adds that MESSAGE auth is only on by default from 1.10.7, and that `auth-messages=false` removes the need for an account |
| Passwordless vanilla `default` user, "vanilla ships two" | Confirmed: `default` and the Skinny example user `SEP001120AABBCC`. Both are passwordless unless `allow-empty-password` is false for the user |
| 1.11.3 adds `enable-chat-api-proto`, off by default, 403 and log | Confirmed. Also missing from the report: 1.11.3 adds the same param to mod_verto (verto clients had the same api proto route), and bounds the To-user split |
| Mitigations: `enable-chat=false`, remove passwordless users, `disable_system_api_commands=true` | Names correct. `disable_system_api_commands` is a global variable (X-PRE-PROCESS set, for example in vars.xml), not a SIP profile setting. Per the advisory only `enable-chat=false` stops the route. The other two only narrow it |
| Allow SIP only from trunk IPs | Not in any primary source. It is reasonable network hygiene, but note that the advisory says the inbound ACL does not cover MESSAGE, so do it at the firewall or SBC, not with the FreeSWITCH ACL |
| Hunt for `api+` and system/bgapi/lua/load in MESSAGE To users | Our own hunting heuristic, not vendor IOCs. The advisory publishes no IOCs. The vendor 403 log line above is the only vendor-provided log text, and it appears only on 1.11.3 or later |

## Contents

| Path | Purpose |
|---|---|
| `kql/freeswitch-cve-2026-116779-hunting.kql` | Q1 Syslog MESSAGE to `api+` or keyword To user, plus the 1.11.3 rejection line; Q2 CommonSecurityLog (SBC/IDS) SIP MESSAGE; Q3 DeviceProcessEvents children of freeswitch; Q4 DeviceFileEvents on dialplan, directory, autoload_configs, sip_profiles and scripts; Q5 DeviceNetworkEvents outbound from freeswitch to non-SIP ports; Q6 inventory (TVM plus running processes) with version status |
| `sigma/*.yml` | 5 rules: api+ MESSAGE keywords, 1.11.3 rejection keyword, freeswitch child process, config file change, outbound non-SIP connection |
| `sigma/converted/` | Kusto output for no pipeline, microsoft_xdr, microsoft_365_defender, sentinel_asim and azure_monitor |
| `scripts/check-freeswitch.sh` | Bash checker |
| `scripts/Check-FreeSWITCH.ps1` | PowerShell 7 checker with the same logic and output |
| `tests/` | Fixtures for every verdict, bash tests, Pester tests and `run_all.sh` |

### Sigma results

`sigma check`: 0 errors, 0 condition errors, 0 issues. Conversion to Kusto (sigma-cli 3.1.0):
- `lnx_freeswitch_child_process.yml`, `lnx_freeswitch_config_change.yml` and `lnx_freeswitch_outbound_non_sip.yml`
  convert on every pipeline. Caveats: `azure_monitor` maps all three to `SecurityEvent` (a Windows table), which is
  no use for Linux. For the network rule, `microsoft_xdr` and `microsoft_365_defender` silently drop the
  `Initiated` condition, and `sentinel_asim` maps it to `NetworkDirection =~ "true"`, which will not match ASIM
  values. Prefer KQL Q3 to Q5.
- `freeswitch_sip_message_api_proto.yml` and `freeswitch_api_proto_rejected.yml` (logsource `linux/freeswitch`,
  keywords) fail on microsoft_xdr, microsoft_365_defender, sentinel_asim and azure_monitor ("Unable to determine
  table name from rule"). They convert only without a pipeline, as bare keyword expressions. Use KQL Q1 and Q2.

## Checkers

These are strictly read-only and offline. They read a FreeSWITCH config root (for example `/etc/freeswitch`, or a
copy of one), an optional version string, an inventory CSV, and optional saved logs. They print user ids and
profile names only. They never print password, a1-hash or default_password values.

```
check-freeswitch.sh --conf /etc/freeswitch [--version "$(freeswitch -version)"] [--host pbx1] [--log freeswitch.log ...]
check-freeswitch.sh --version 1.10.12-release
check-freeswitch.sh --csv inventory.csv
check-freeswitch.sh --log freeswitch.log --log capture.txt
pwsh Check-FreeSWITCH.ps1 -Conf /etc/freeswitch [-Version V] [-HostName N] [-Log f1,f2] | -Version V | -Csv f | -Log f
```

What they read, after stripping XML comments:
- `sip_profiles/*.xml` and `autoload_configs/sofia.conf.xml`: `<profile>` blocks with `enable-chat` (absent means
  on), `enable-chat-api-proto` and `auth-messages`/`disable-auth-messages`. Values follow FreeSWITCH
  `switch_true()` rules. A `$${var}` value is reported as undetermined.
- `directory/**/*.xml`: `<user>` blocks with no non-empty `password` or `a1-hash` param, unless
  `allow-empty-password` is false. `type="pointer"` users are skipped.
- Any `*.xml` up to 3 levels deep: `X-PRE-PROCESS set` of `disable_system_api_commands`, and the vanilla
  `default_password=1234` (reported, value never printed).
- `autoload_configs/modules.conf.xml`: whether `mod_lua` and `mod_verto` are loaded. `autoload_configs/verto.conf.xml`:
  `enable-chat-api-proto`.
- Logs: lines with `MESSAGE sip:api+`, `To: <sip:api+` (case-insensitive), a MESSAGE Request-URI user containing
  system, bgapi, lua or load, and the 1.11.3 rejection text. They print the file, line number and the first 160
  characters.

Verdicts and exit codes:
- 0 **PATCHED**: version 1.11.3 or later, and no SIP or verto profile sets `enable-chat-api-proto=true`.
- 1 **LIKELY VULNERABLE**: below 1.11.3 with inbound chat on, listing the reasons (profiles, passwordless users,
  `disable_system_api_commands`, mod_lua, mod_verto). Also used for 1.11.3 or later with
  `enable-chat-api-proto=true`.
- 2 **UNKNOWN**: no version given (or a `1.11.3-dev` build), no profiles found, or `enable-chat` set from a variable.
- 3 **MITIGATED**: `enable-chat=false` on every SIP profile. This holds even without a version. Upgrade anyway, and
  note that it does not cover mod_verto.
- 4 **LOG HIT**: overrides the others. A rejection-only result means the attempts were blocked by 1.11.3.
- 5 usage or read error. CSV mode exits with the worst row (1 > 2 > 3 > 0).

Removing passwordless users or setting `disable_system_api_commands` never changes the verdict to MITIGATED,
because the advisory says neither closes the route.

CSV header: `host,version,chat_enabled_profiles,chat_api_proto_profiles,passwordless_users,disable_system_api_commands,mod_lua`
(counts or `unknown`, and yes/no/unknown).

Limits: version logic compares upstream versions only. Distro or vendor backports are not recognised, so a
backported build may show as unfixed. Domain or group level `password` params are not considered. Profiles
loaded from somewhere other than `sip_profiles/` or `sofia.conf.xml` (for example `xml_curl`) are not seen. A clean
log is not proof, because sofia does not log SIP unless siptrace is on.

## Tests

`bash tests/run_all.sh` runs: shellcheck, PSScriptAnalyzer, 60 bash tests, 30 Pester tests, a guard against `api+`
paired with a real API name in fixtures or scripts, guards against network and write primitives in the scripts,
a guard against em and en dashes, 12 bwrap runs with `--unshare-net` and a read-only root, a sha256 before and
after check of the pack, and `sigma check` plus conversions. The fixtures are synthetic. `FIXTURE_ONLY` is not a
command, and the IPs are from RFC 5737 documentation ranges. They are not IOCs.

## Remediation

1. Upgrade to FreeSWITCH 1.11.3 or later and restart it. Do not set `enable-chat-api-proto=true` unless every peer
   on that profile is fully trusted.
2. If you cannot patch at once: set `enable-chat=false` on every SIP profile that does not need inbound chat (this
   closes the SIP route). Remove directory users that have no password or a1-hash (vanilla `default` and
   `SEP001120AABBCC`). Set `disable_system_api_commands=true` in vars.xml. Change the vanilla `default_password`.
   Allow SIP only from known trunk and carrier IPs at the firewall or SBC. These steps reduce the risk but do not
   replace patching.
3. Hunt with the KQL and checkers. If you find api chat proto activity, child shells of freeswitch, changed
   dialplan or directory files, or unexpected outbound connections or calls: isolate the host, preserve logs and
   captures, rotate every SIP, trunk, ESL and directory credential and any secrets in its config, rebuild or
   patch, and check carrier bills.
