Fortinet CVE-2025-25249 – Actively Exploited CAPWAP Remote Code Execution

Threat Overview

CISA added CVE-2025-25249 to the Known Exploited Vulnerabilities Catalog on September 9, 2026, establishing exploitation in the wild. The vulnerability is a heap-based buffer overflow in Fortinet’s cw_acd daemon associated with CAPWAP wireless-controller functionality. Fortinet describes unauthenticated remote code or command execution through specially crafted requests. Public technical reporting identifies CAPWAP Control traffic on UDP/5246 as the relevant service exposure. Fortinet and CISA have not published a stable exploit-packet signature or universal attacker IOC set, so this hunt prioritizes exposure, anomalous CAPWAP traffic, daemon instability, and post-compromise behavior.

References

Impacted Systems

Products: FortiOS, FortiSwitchManager, and FortiSASE. Component: cw_acd / CAPWAP wireless-controller functionality; UDP/5246 CAPWAP Control is the relevant network service. Authentication: unauthenticated. Publicly listed affected FortiOS branches include 7.6.0-7.6.3, 7.4.0-7.4.8, 7.2.0-7.2.11, 7.0.0-7.0.17, and 6.4.x through 6.4.16; FortiSwitchManager 7.2.0-7.2.6 and 7.0.0-7.0.5 are also listed. Public fixed-version reporting indicates FortiOS 7.6.4+, 7.4.9+, 7.2.12+, 7.0.18+, and FortiSwitchManager 7.2.7+. Validate the current Fortinet PSIRT advisory for the exact supported upgrade target.

Why this matters

FortiGate-class perimeter devices and centralized network-management systems are high-value targets. Successful code execution on a security gateway can provide a privileged foothold with network visibility and control.

Exploitation Status

Confirmed exploitation in the wild based on CISA KEV inclusion on September 9, 2026. CISA does not identify a threat actor or ransomware association, and no vendor-validated universal exploit packet signature is publicly available.

What this hunt looks for

New or high-rate sources reaching known Fortinet CAPWAP Control services, raw Syslog evidence of cw_acd crashes or faults, and unusual outbound traffic from known Fortinet appliance IPs.

Required logs

  • Fortinet/FortiAnalyzer CEF or CommonSecurityLog covering CAPWAP traffic
  • Fortinet appliance Syslog for daemon crash/fault telemetry
  • Upstream firewall telemetry covering UDP/5246 when appliance logs are unavailable

First Pass – New Sources Reaching Fortinet CAPWAP Control

let FortinetIPs = dynamic(["REPLACE_WITH_FORTIGATE_OR_FORTISWITCHMANAGER_IP"]);
let Baseline = CommonSecurityLog
| where TimeGenerated between (ago(30d) .. ago(2d))
| where DestinationIP in (FortinetIPs)
| where DestinationPort == 5246
| summarize by SourceIP, DestinationIP;
CommonSecurityLog
| where TimeGenerated > ago(2d)
| where DestinationIP in (FortinetIPs)
| where DestinationPort == 5246
| join kind=leftanti Baseline on SourceIP, DestinationIP
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Connections=count(), Actions=make_set(DeviceAction,20) by SourceIP, DestinationIP
| order by FirstSeen desc

CAPWAP Control Traffic to Known Fortinet Devices

let FortinetIPs = dynamic(["REPLACE_WITH_FORTIGATE_OR_FORTISWITCHMANAGER_IP"]);
CommonSecurityLog
| where TimeGenerated > ago(14d)
| where DestinationIP in (FortinetIPs)
| where DestinationPort == 5246
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Connections=count(), Actions=make_set(DeviceAction,20), Protocols=make_set(Protocol,10) by SourceIP, DestinationIP
| order by Connections desc

High-Rate CAPWAP Probing or Packet Bursts

let FortinetIPs = dynamic(["REPLACE_WITH_FORTIGATE_OR_FORTISWITCHMANAGER_IP"]);
CommonSecurityLog
| where TimeGenerated > ago(14d)
| where DestinationIP in (FortinetIPs)
| where DestinationPort == 5246
| summarize Connections=count(), Actions=make_set(DeviceAction,20) by SourceIP, DestinationIP, bin(TimeGenerated,5m)
| where Connections >= 25
| order by TimeGenerated desc

cw_acd Crash or Memory-Fault Indicators in Syslog

let FortinetHosts = dynamic(["REPLACE_WITH_FORTINET_HOSTNAME"]);
Syslog
| where TimeGenerated > ago(14d)
| where array_length(FortinetHosts) == 0 or Computer in~ (FortinetHosts)
| where SyslogMessage contains "cw_acd" and SyslogMessage has_any ("segfault","crash","core","fault","abort","killed","memory")
| project TimeGenerated, Computer, ProcessName, Facility, SeverityLevel, SyslogMessage
| order by TimeGenerated desc

Raw Syslog References to CAPWAP or cw_acd

let FortinetHosts = dynamic(["REPLACE_WITH_FORTINET_HOSTNAME"]);
Syslog
| where TimeGenerated > ago(14d)
| where array_length(FortinetHosts) == 0 or Computer in~ (FortinetHosts)
| where SyslogMessage has_any ("cw_acd","CAPWAP","5246")
| project TimeGenerated, Computer, ProcessName, Facility, SeverityLevel, SyslogMessage
| order by TimeGenerated desc

Rare Outbound Destinations From Fortinet Appliances

let FortinetIPs = dynamic(["REPLACE_WITH_FORTIGATE_OR_FORTISWITCHMANAGER_IP"]);
let Baseline = CommonSecurityLog
| where TimeGenerated between (ago(30d) .. ago(2d))
| where SourceIP in (FortinetIPs)
| summarize by DestinationIP, DestinationPort;
CommonSecurityLog
| where TimeGenerated > ago(2d)
| where SourceIP in (FortinetIPs)
| join kind=leftanti Baseline on DestinationIP, DestinationPort
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Connections=count(), Actions=make_set(DeviceAction,20) by SourceIP, DestinationIP, DestinationPort
| order by FirstSeen desc

Detection Notes

The highest-value first pass is unexpected traffic to UDP/5246 on known affected devices, especially from sources not present in the prior baseline. This is not proof of exploitation because legitimate FortiAP/FortiGate CAPWAP traffic can use the same service. cw_acd crash indicators increase suspicion but memory corruption does not guarantee a visible crash. Rare outbound traffic is a post-compromise pivot only. Without Fortinet traffic logs, FortiAnalyzer/CEF data, appliance Syslog, or upstream firewall telemetry covering UDP/5246, Sentinel may not directly observe the exploitation attempt.

Leave a comment