FortiMail CVE-2026-104286: Active Exploitation Hunt

Threat Overview

Fortinet disclosed CVE-2026-104286 on October 1, 2026, a critical path-traversal and null-byte handling flaw in the FortiMail GUI/Identity Based Encryption (IBE) surface. A remote, unauthenticated attacker can send crafted HTTP or HTTPS requests that write arbitrary files to the appliance; writes to executable or loader-controlled locations can lead to unauthorized code or command execution. Fortinet rates the issue CVSS 9.8 and states that exploitation is occurring in the wild. CISA added it to the Known Exploited Vulnerabilities catalog on October 1 with active exploitation, automatable exploitation, and total technical impact.

The vendor-published campaign evidence is unusually concrete: added or modified binaries and configuration files, an ld.so.preload entry, a root cron command referencing /migadmin, command-line configuration of a remote archive account named archive234, an IBE base64-decoding exception, and two attacker-associated IP addresses. These indicators show that observed exploitation progressed beyond scanning to persistence-capable system modification and remote mail-archive configuration. Fortinet has not publicly attributed the activity or stated its scale.

References

Impacted Systems

  • Vendor/product: Fortinet FortiMail physical or virtual email-security appliances; self-managed/on-premises deployment.
  • Affected versions: FortiMail 8.0.0–8.0.1, 7.6.0–7.6.6, 7.4.0–7.4.8, and 7.2.0–7.2.9, per the current vendor/NCSC advisory data.
  • Required feature: Identity Based Encryption (IBE) enabled.
  • Exposure/prerequisite: An attacker can reach the vulnerable FortiMail HTTP/HTTPS GUI/IBE surface; no authentication or user interaction is required.
  • Fixed targets: 8.0.2+, 7.6.7+, and 7.4.9+ when available; 7.2 deployments should migrate to 7.4 or later. Verify availability in Fortinet’s live advisory before change execution because several builds were listed as upcoming at publication.
  • Explicitly out of scope: Other Fortinet products are not covered by FG-IR-26-175. A FortiMail appliance with IBE disabled does not meet the documented feature prerequisite, but still requires version and configuration validation.

Why this matters

FortiMail is commonly internet-facing and processes sensitive message content. The vulnerability is unauthenticated, actively exploited, and capable of arbitrary file write. The observed campaign modified the dynamic-loader configuration and FortiMail web configuration and created a remote archive destination, presenting risks of persistent appliance control and email collection.

Exploitation Status

Confirmed active exploitation. Fortinet states that CVE-2026-104286 is exploited in the wild. CISA independently records active exploitation. Published indicators include IPs 79.141.169.187 and 45.129.0.192; added files /data/lib/liblog.so, /data/bin/webconsole, /data/bin/mailservice, and /data/etc/ld.so.preload; modified /bin/smit, /data/etc/httpd.conf, and /data/migadmin.tar.gz; a cron command beginning O=/migadmin; and creation of archive account archive234 with remote directory /uploads. These indicators describe observed activity, not every possible exploitation path.

What this hunt looks for

Fortinet-published log fragments, exact file hashes and paths, attacker IPs, remote archive configuration, path-traversal and null-byte HTTP patterns, root cron behavior, and rare FortiMail egress.

Required logs

FortiMail native event, system, and configuration logs forwarded to Syslog; upstream firewall, WAF, proxy, or flow telemetry in CommonSecurityLog; and, where supported, appliance-level DeviceFileEvents, DeviceProcessEvents, and DeviceNetworkEvents.

Hunt 1 — Fortinet-published FortiMail log indicators

let lookback = 30d;
Syslog
| where TimeGenerated >= ago(lookback)
| where SyslogMessage has_any ("O=/migadmin", "archive234", "79.141.169.187", "Invalid Base64 Encoding at pos 0", "User admin logged out from (null)")
| project TimeGenerated, Computer, HostName, Facility, SeverityLevel, ProcessName, SyslogMessage
| order by TimeGenerated desc

Hunt 2 — Exact published malicious file hashes

let lookback = 30d;
let sha256 = dynamic(["8015f34dc84922b03688399d7f9fe7a00361789f7e420c7e2a2cdb23e75cef84","77324ac428bde86d351fc5fc06f6d64a6bfe737dfb2743df1d4c5ac2418a5b6a","7a6cea9f5c9e2e9994d4e3c4da73f86cf5acd05ea5d312c066c9d1dafd69ee38","4000276a150a165d3c2537d1e19fb393c4de8333076a16655e28059cae82157b","703e97c64e61e41dc3aaba580d82bb2aa7b6a11b54ee6fb467ed5d5a3bffdef5","8953ec7960b09f544a880b072ad4e6cfda7a8303f486251d3478dcfdfbac23b6","d6fe51c22b91776f4c961ea58bcac5917f15d560a619d7ce726d3d51795609d3"]);
DeviceFileEvents
| where Timestamp >= ago(lookback)
| where SHA256 in~ (sha256)
| project Timestamp, DeviceName, ActionType, FolderPath, FileName, SHA256, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc

Hunt 3 — Published FortiMail file additions and modifications by path

let lookback = 30d;
let paths = dynamic(["/data/lib/liblog.so","/bin/smit","/data/bin/webconsole","/data/bin/mailservice","/data/etc/httpd.conf","/data/etc/ld.so.preload","/data/migadmin.tar.gz"]);
DeviceFileEvents
| where Timestamp >= ago(lookback)
| extend FullPath = strcat(FolderPath, "/", FileName)
| where FullPath in~ (paths)
| where ActionType in~ ("FileCreated", "FileModified", "FileRenamed", "Created", "Modified", "Renamed")
| project Timestamp, DeviceName, ActionType, FullPath, SHA256, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc

Hunt 4 — Network contact with published attacker IPs

let lookback = 90d;
let iocs = dynamic(["79.141.169.187", "45.129.0.192"]);
DeviceNetworkEvents
| where Timestamp >= ago(lookback)
| where RemoteIP in (iocs)
| project Timestamp, DeviceName, ActionType, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine, LocalIP, LocalPort, RemoteIP, RemotePort, RemoteUrl
| order by Timestamp desc

Hunt 5 — Firewall, proxy, or appliance events involving published IPs

let lookback = 90d;
let iocs = dynamic(["79.141.169.187", "45.129.0.192"]);
CommonSecurityLog
| where TimeGenerated >= ago(lookback)
| where SourceIP in (iocs) or DestinationIP in (iocs)
| project TimeGenerated, DeviceVendor, DeviceProduct, DeviceName, Activity, DeviceAction, SourceIP, SourcePort, DestinationIP, DestinationPort, RequestMethod, RequestURL, Message
| order by TimeGenerated desc

Hunt 6 — Path traversal and null-byte patterns to FortiMail HTTP/S

let lookback = 30d;
CommonSecurityLog
| where TimeGenerated >= ago(lookback)
| where DestinationPort in (80, 443) or ApplicationProtocol =~ "HTTPS"
| extend Uri = tolower(coalesce(RequestURL, Message, AdditionalExtensions))
| where Uri has_any ("../", "%2e%2e", "%252e", "%00", "%2500")
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Requests=count(), Samples=make_set(Uri, 10) by SourceIP, DestinationIP, DestinationPort, DeviceVendor, DeviceProduct, DeviceAction
| order by LastSeen desc

Hunt 7 — Root cron or shell activity referencing migadmin and loader persistence

let lookback = 30d;
DeviceProcessEvents
| where Timestamp >= ago(lookback)
| extend Cmd = tolower(ProcessCommandLine)
| where Cmd has_any ("/migadmin", "ld.so.preload", "/data/bin/webconsole", "/data/bin/mailservice", "archive234")
| where AccountName =~ "root" or InitiatingProcessFileName in~ ("cron", "crond", "sh", "bash")
| project Timestamp, DeviceName, AccountName, FileName, FolderPath, ProcessCommandLine, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc

Hunt 8 — Remote archive configuration and egress correlation

let lookback = 30d;
let archiveEvents = Syslog
| where TimeGenerated >= ago(lookback)
| where SyslogMessage has_all ("archive account", "destination[remote]")
| project ArchiveTime=TimeGenerated, Computer, ArchiveMessage=SyslogMessage;
let egress = CommonSecurityLog
| where TimeGenerated >= ago(lookback)
| where DestinationPort !in (25, 53, 123)
| project EgressTime=TimeGenerated, SourceHostName, SourceIP, DestinationIP, DestinationPort, DeviceAction;
archiveEvents
| join kind=leftouter (egress) on $left.Computer == $right.SourceHostName
| where isnull(EgressTime) or EgressTime between (ArchiveTime-15m .. ArchiveTime+2h)
| project ArchiveTime, Computer, ArchiveMessage, EgressTime, SourceIP, DestinationIP, DestinationPort, DeviceAction
| order by ArchiveTime desc

Hunt 9 — Rare new outbound destinations from FortiMail sources

let baseline = 30d;
let recent = 1d;
let known = CommonSecurityLog
| where TimeGenerated between (ago(baseline) .. ago(recent))
| summarize by SourceIP, DestinationIP;
CommonSecurityLog
| where TimeGenerated >= ago(recent)
| where DestinationPort !in (25, 53, 123)
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Connections=count(), Ports=make_set(DestinationPort, 20), Actions=make_set(DeviceAction, 10) by SourceIP, DestinationIP
| join kind=leftanti known on SourceIP, DestinationIP
| order by LastSeen desc

Detection Notes

  • Hunt 1 and the exact hash/IP matches in Hunts 2, 4, and 5 are highest signal. A published indicator match should be treated as potential compromise, not merely vulnerable exposure.
  • Hunt 6 is behaviorally relevant but can be noisy because scanners and benign malformed requests generate traversal encodings. Confirm that the destination is a FortiMail GUI/IBE listener and correlate with subsequent file, config, cron, or egress evidence.
  • The published /data/etc/ld.so.preload artifact indicates dynamic-loader persistence. A file match without appliance triage cannot establish whether the loaded object executed or what it affected.
  • Appliance Syslog forwarding may omit debug-level events, configuration detail, or historical entries. TLS-encrypted requests may prevent upstream WAF/proxy inspection.
  • Hunts 2, 3, 4, and 7 require appliance-level endpoint telemetry that is often unavailable or unsupported. In that case, FortiMail-native logs and upstream network data are the primary Sentinel evidence.
  • Hunt 8 depends on host identifiers aligning between Syslog and CommonSecurityLog; adapt the join to the local normalization model. Hunt 9 must be scoped to known FortiMail source IPs before operational use.
  • Fixed-build information changed during the first hours of disclosure. Use Fortinet’s current advisory as the authoritative pre-change reference.