-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 NEFARIOUSPLAN-CANONICAL-V1 {"body_md":"## The chat layer dispatches by case-insensitive name lookup\n\nInbound SIP `MESSAGE` lands on `sofia_presence_handle_sip_i_message()`. On a stock profile, chat is on by default; `sofia.c` line 4656 of the pre-patch tree runs `sofia_set_pflag(profile, PFLAG_ENABLE_CHAT)` during profile construction. Both vanilla profiles (`internal.xml`, `external.xml`) hit that path.\n\nThe pre-patch dispatcher reads the To user and splits it on the first plus sign:\n\n```c\n/* src/mod/endpoints/mod_sofia/sofia_presence.c, pre-patch */\nchar proto[512] = SOFIA_CHAT_PROTO;\n...\nif ((p = strchr(to_user, '+')) && p != to_user && strlen(to_user) < sizeof(proto)) {\n switch_copy_string(proto, to_user, sizeof(proto));\n p = strchr(proto, '+');\n *p++ = '\\0';\n ...\n}\n```\n\nThe left half of the plus becomes `proto`. The right half becomes the address. There is no allow-list on `proto`. There is a comment above the block explaining the 512-byte truncation guard, and nothing about which protocol names are legal. A hundred and forty lines later, the dispatcher commits:\n\n```c\n/* src/mod/endpoints/mod_sofia/sofia_presence.c:5021, pre-patch */\nswitch_core_chat_send(proto, event);\n```\n\n`switch_core_chat_send` looks `proto` up in the chat interface hash. That hash is built with `switch_core_hash_init_nocase()`, which the fix commit itself notes: \"The proto compare is case-insensitive, matching the chat interface registry.\" The lookup accepts whatever name is in the hash, in whatever case.\n\nOne of the names in the hash is `api`.\n\n## The `api` chat interface's handler is `switch_api_execute`\n\n`mod_dptools` registers two chat interfaces at module load. The registrations are three lines apart:\n\n```c\n/* src/mod/applications/mod_dptools/mod_dptools.c:6617-6618 */\nSWITCH_ADD_CHAT(chat_interface, \"event\", event_chat_send);\nSWITCH_ADD_CHAT(chat_interface, \"api\", api_chat_send);\n```\n\n`api_chat_send`'s body is a URL decode, a space split, and a call into the FreeSWITCH API dispatcher:\n\n```c\n/* src/mod/applications/mod_dptools/mod_dptools.c:4669-4707 */\nstatic switch_status_t api_chat_send(switch_event_t *message_event)\n{\n ...\n if (to) {\n char *v = NULL;\n switch_stream_handle_t stream = { 0 };\n char *cmd = NULL, *arg;\n\n if (!(v = switch_core_get_variable_dup(to))) {\n v = strdup(to);\n }\n cmd = v;\n switch_assert(cmd);\n\n switch_url_decode(cmd);\n\n if ((arg = strchr(cmd, ' '))) {\n *arg++ = '\\0';\n }\n\n SWITCH_STANDARD_STREAM(stream);\n switch_api_execute(cmd, arg, NULL, &stream);\n ...\n }\n return SWITCH_STATUS_SUCCESS;\n}\n```\n\n`switch_api_execute` is the public dispatcher for every `SWITCH_ADD_API` registration in the FreeSWITCH tree. One of those registrations is `system`:\n\n```c\n/* src/mod/applications/mod_commands/mod_commands.c:7633 */\nSWITCH_ADD_API(commands_api_interface, \"system\", \"Execute a system command\",\n system_function, SYSTEM_SYNTAX);\n```\n\n`system_function` calls `switch_stream_system(cmd, stream)`. `switch_stream_system` is `popen`:\n\n```c\n/* src/switch_core.c:3545-3576 */\nSWITCH_DECLARE(int) switch_stream_system(const char *cmd, switch_stream_handle_t *stream)\n{\n ...\n FILE* pipe = popen(cmd, \"r\");\n if (!pipe) return 1;\n ...\n}\n```\n\nThe nine-line call chain from an attacker-controlled `To:` user to `/bin/sh -c` is: `sofia_presence.c` strchr, `sofia_presence.c` switch_copy_string, `sofia_presence.c` switch_core_chat_send, hash lookup, `mod_dptools.c` switch_url_decode, `mod_dptools.c` switch_api_execute, `mod_commands.c` system_function, `switch_core.c` switch_stream_system, libc `popen`. Four of those lines are in files SignalWire did not touch. Five are in the one file the patch did touch.\n\nThe PoC that confirmed it runs as a single SIP `MESSAGE` with no body, from an authenticated digest session on port 5060:\n\n```\nMESSAGE sip:localhost SIP/2.0\nTo: \nFrom: \n...\n```\n\nThe daemon answers `202`, enqueues the chat event, and the worker thread executes `id` inside the running FreeSWITCH process. `popen` returns stdout to the stream; the chat layer wraps it in an outbound `MESSAGE` and sends it back to the caller. Command execution and output exfiltration in one SIP round-trip, with the caller's digest authentication as the only gate.\n\n## The patch is 38 lines and the sink is not one of them\n\nCommit `7015f6a7` is dated 2026-08-26 and signed by Dmitry Verenitsin. It modifies eight files. The diff is reproduced here in full, abbreviated at the context lines:\n\n```diff\n--- a/src/mod/endpoints/mod_sofia/sofia_presence.c\n+++ b/src/mod/endpoints/mod_sofia/sofia_presence.c\n@@ -4879,6 +4879,24 @@ void sofia_presence_handle_sip_i_message(...)\n p = strchr(proto, '+');\n *p++ = '\\0';\n \n+ if (!strcasecmp(proto, \"api\") && !sofia_test_pflag(profile, PFLAG_ENABLE_CHAT_API_PROTO)) {\n+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING,\n+ \"Profile [%s] rejected a MESSAGE from %s@%s (%s:%d) addressed to the 'api' chat proto [%s]. \"\n+ \"Set enable-chat-api-proto=true on the profile to permit it.\\n\",\n+ profile->name, switch_str_nil(from_user), switch_str_nil(from_host), network_ip, network_port, p);\n+\n+ nua_respond(nh, SIP_403_FORBIDDEN, NUTAG_WITH_THIS_MSG(de->data->e_msg), TAG_END());\n+ ...\n+ return;\n+ }\n+\n if ((to_addr = strdup(p))) {\n if ((p = strchr(to_addr, '+'))) {\n *p = '@';\n```\n\nThe `sofia.c` side adds the XML-param-to-pflag wiring (six lines). `mod_sofia.h` adds one enum value, `PFLAG_ENABLE_CHAT_API_PROTO`. The remaining fourteen lines of the diff are XML comments in four vanilla profiles and the mod_sofia sample `sofia.conf.xml`:\n\n```xml\n\n\n```\n\nCount the files the patch did not touch. `mod_dptools.c`, where the `api` chat interface is registered and where its handler calls `switch_api_execute`: unchanged. `mod_commands.c`, where `system` is registered as an API and where it bottoms out in `popen`: unchanged. `switch_core.c`, where `switch_stream_system` calls `popen`: unchanged. The chat registry's case-insensitive lookup: unchanged. The default state of `PFLAG_ENABLE_CHAT` in profile construction: unchanged; stock profiles still process inbound chat, they now 403 on the `api` proto specifically.\n\nA vanilla 1.11.3 profile renders the new param like this:\n\n```xml\n\n\n```\n\nTwo XML comment tokens. Delete `` from the second line and the parser reads an active `` element whose value is `true`. The 403 branch does not fire. The pre-patch code path runs. The `api` chat interface answers. `switch_api_execute` runs. `popen` runs.\n\nThis is **Default Is The Mitigation**: the patched binary contains the unpatched primitive, and the toggle that gates it is one XML comment away. The pattern's other exhibits move a numeric constant (vLLM's [CVE-2026-5497](/posts/vllm-cve-2026-5497-the-fix-is-the-default) raised the frame cap from unbounded to 32; DBI's CVE-2026-10879 raised a `strlen * 3` buffer to `strlen * 6 + 16`) or flip a vendor boolean from false to true inside the same configuration XML the operator already edits (SAP Visual Composer's CVE-2026-89110 moved `SecurityMode` from `false` to the documented `true`). CVE-2026-116779 is the pattern at the SIP-protocol scale: the sink is a SIP `MESSAGE`, the toggle is an XML comment pair in `/etc/freeswitch/sip_profiles/internal.xml`, and the operator who reads the vendor comment \"enable only for trusted peers\" and uncomments the line to permit their own SIP peers to use the `api` chat interface has shipped the pre-patch binary.\n\nThe vendor comment labels the opt-in with a design judgment: enable it for trusted peers. SignalWire did not retire the `api` chat proto. They retitled it. The feature stays. It is now documented, and the documentation is a six-word vote of confidence that the design is legitimate for a sufficiently trusted peer set.\n\n## PR:N is correct because the stock directory ships the credentials\n\nThe CVSS vector is `AV:N/AC:L/PR:N/UI:N`. Privileges Required: None. The GHSA advisory qualifies this: \"any authenticated profile account is sufficient; stock directory users without password material can also remove that barrier in affected configurations.\"\n\nThe stock directory ships with password material. `conf/vanilla/directory/default/` contains twenty-four user XML files; 1000.xml through 1019.xml all declare:\n\n```xml\n\n \n \n ...\n \n ...\n\n```\n\n`conf/vanilla/vars.xml` defines the variable:\n\n```xml\n\n\n```\n\nThe vendor-shipped password for twenty users on every vanilla FreeSWITCH install is the literal string `1234`. The variable name declares the role; the vars comment asks the operator to change it; the stock directory depends on it; the authenticated profile account requirement that the advisory cites is satisfied by digesting as `1000:1234`.\n\nPR:N is correct not because the mechanism bypasses authentication, but because the vendor shipped the authentication. The call-center operator who downloaded the FreeSWITCH getting-started image in 2024, opened port 5060 to the SBC, and forgot to prune extensions 1000 through 1019 is the authenticated profile account the advisory's qualifying clause names. The telecom reseller who provisioned a thousand branch PBXes from the same stock image and forgot it a thousand times is the thousand authenticated profile accounts the mass-exploitation campaigns are digesting against.\n\nThe chat-API proto is case-insensitive. The digest challenge is standard. The `To:` header does the rest.\n\n## The advisory publishes 63 days after the fix\n\nThe fix commit lands on master on 2026-08-26. The GHSA advisory publishes on 2026-10-28, sixty-three days later, credited to Örn Arnarson (Reporter) and Gunnlaugur (Analyst). NVD publishes on 2026-11-05. CISA adds the KEV entry with a seven-day remediation deadline of 2026-11-12. The advisory records the discovery mode in the same paragraph as the mechanism: \"Exploitation has been observed in the wild.\"\n\nThe pipeline this timeline describes is **Disclosure After Exploitation**. The fix is upstream for sixty-three days as a public commit titled `[mod_sofia] Add enable-chat-api-proto to gate the api chat proto` before the advisory names it a vulnerability. Any operator watching the master branch could have read the commit message, read the comment in the vanilla profile XML, and reproduced the pre-patch primitive with a single `MESSAGE` the day the commit landed. The advisory acknowledges that someone did. The public disclosure is documentation of a thing that was already happening.\n\nThe twelve-day window between NVD publication and the KEV deadline is not a patch window. It is a window to inventory which stock profiles on which appliances have which commented-out values. The operators who uncommented `` because the vendor comment told them it was fine for trusted peers are inside the patched version and outside the mitigation. The scan signature for 1.11.3-but-vulnerable is identical to the scan signature for 1.11.2-but-patched-at-the-config-layer: send an `api+system%20id` MESSAGE and read the 403 or the 202.\n\n## This was always chat routing\n\nThe CVE calls this a command injection. CWE-77 and CWE-78 both appear on the record. Neither word quite fits. There is no injection. The caller selects a chat protocol by string; one of the registered chat protocols is named `api`; its registered handler is the FreeSWITCH API dispatcher; one of the registered FreeSWITCH APIs is `system`; the system API's registered backend is `popen`. Every layer is a documented feature of the chat subsystem registering against the API subsystem registering against the shell. The exploit traverses five registrations. The patch gates one of them, conditionally, behind one boolean, defaulted off, documented as appropriate for trusted peers.\n\nSignalWire did not fix the `api` chat proto. They shipped it as the product. The fix is the comment markers. Delete four characters and 1.11.3 is 1.11.2.\n\nPoC: [hackzuoji/freeswitch-cve-2026-116779-POC](https://github.com/hackzuoji/freeswitch-cve-2026-116779-POC)","closing_line":"The fix is the comment markers. Delete four characters and 1.11.3 is 1.11.2.","hook_md":"The pre-patch call chain fires from an inbound SIP `MESSAGE` to a shell command in nine lines across two files, and the 1.11.3 fix for CVE-2026-116779 does not change any of those nine lines. The `api` chat interface is still registered in `mod_dptools.c`. The handler is still `switch_api_execute`, and `system` still bottoms out at `popen(cmd, \"r\")`. SignalWire's fix is a per-profile boolean named `enable-chat-api-proto`, shipped commented out in the four vanilla profiles. The GHSA advisory records the discovery mode as \"exploitation has been observed in the wild,\" and CISA pinned the KEV remediation deadline to 2026-11-12.","post_id":853,"slug":"freeswitch-api-chat-proto-is-a-feature-flag","title":"CVE-2026-116779: FreeSWITCH Shipped the api Chat Proto. 1.11.3 Shipped the Checkbox.","type":"initial","unreadable_sentence":"The fix is the comment markers. Delete four characters and 1.11.3 is 1.11.2."} -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQRf0htP5+SjynlxywneZjl4jgkQJgUCavITmAAKCRDeZjl4jgkQ Jn7PAQCmkLyuZaFHArRS6rL7mfnQ0TM2KLxieMtTsn1HIyVFvQEAv9UzNDTkIHUA kinUX4Q5myd9srsW6THCN49hTvW3DwA= =BEBP -----END PGP SIGNATURE-----