Advisories | 1898 & Co.

Threat Hunt Plan: Fortinet FortiProxy, FortiPAM and FortiSwitch Manager — HTTP/2 Memory Exhaustion Denial of Service

Written by The 1898 & Co. Team | August 17, 2026

Date: August 17, 2026 | Revision 1.0

Hunt Objective and Scope

This hunt seeks evidence that an unauthenticated remote attacker has attempted or achieved a denial-of-service condition against Fortinet FortiProxy, FortiPAM, or FortiSwitch Manager appliances by exploiting CVE-2026-49975, the memory-exhaustion flaw in the Apache HTTP Server mod_http2 module publicly nicknamed the "HTTP/2 Bomb." The flaw permits a small number of crafted HTTP/2 header frames on a single connection to force a disproportionate memory allocation on the server, exhausting available memory and interrupting service. Fortinet published the issue as FG-IR-26-163 on August 12, 2026 with a vendor CVSS v3.1 score of 5.8; NIST scores the upstream Apache defect at CVSS v3.1 7.5. The hunt covers both the confirmed-affected Fortinet products and any standalone Apache HTTP Server instance in the estate running an affected version, because the same defect and the same exploitation technique apply to both.

Environment in scope: all internet-facing and internally reachable FortiProxy (7.2 all versions, 7.4.0–7.4.14, 7.6.0–7.6.6), FortiPAM (1.0–1.8 all versions, 1.9.0–1.9.1), and FortiSwitch Manager (7.2.0–7.2.9) instances; the network segments those appliances mediate, including any IT/OT boundary they enforce; all hosts running Apache HTTP Server 2.4.17 through 2.4.67; and the organization's public IP space and cloud asset inventory for the external-exposure leg. Where these appliances sit at an enterprise-to-control-network boundary, the OT segments behind them are in scope for availability-impact assessment.

Time window: 90 days back from execution, extended to cover June 8, 2026 — the upstream Apache disclosure date and the point at which public proof-of-concept exploit code first became available — through to the present. Any confirmed external exposure discovered in Hypothesis 1 expands the window for that specific asset to the full retention available, because continuous exposure across the disclosure window means the asset should be treated as presumptively enumerated by opportunistic scanners.

Scope note on sensor coverage: Fortinet appliances do not run a CrowdStrike Falcon sensor. All CrowdStrike Falcon LogScale (CQL) collection against the appliances themselves depends on Fortinet syslog being forwarded into NG-SIEM through a third-party data connector; the endpoint-telemetry queries below target hosts behind or adjacent to the appliances, not the appliances. Confirm the connector is present and populating before treating any zero-result appliance query as a negative.

Hypotheses and Hunt Procedures

Hypothesis 1: an external actor can reach an internet-facing FortiProxy, FortiPAM, or FortiSwitch Manager instance, observable as an exposed service in passive internet-scan indices. Execute FIRST; its output scopes the internal hypotheses.

MITRE ATT&CK: Reconnaissance | T1595 Active Scanning — defender-side passive discovery of what an actor would enumerate · Initial Access | T1133 External Remote Services — an exposed appliance management or proxy listener is the reachable surface · ICS | T0883 Internet Accessible Device — where the appliance fronts a control network.

Collection Queries — passive external indices (STRICTLY passive; never probe target infrastructure):

Run each recipe unscoped first to confirm the filter actually matches the product, then re-run scoped to your own estate. Fortinet product strings vary across the indices and FortiProxy and FortiPAM are less consistently fingerprinted than FortiGate, so read the returned banners before trusting a zero result.

Shodan:
product:"Fortinet"
http.title:"FortiProxy"
http.title:"FortiPAM"
http.title:"FortiSwitch Manager"
http.html:"fortinet" port:443,8443,10443
ssl.cert.subject.CN:"FortiProxy"

 

// scope with: net:<your_public_CIDR> org:"<Your Org Name>" asn:AS<your_ASN>

 

Censys (CenQL — current form; do NOT mix with legacy services.* CSL):
host.services.software.vendor: "Fortinet"
host.services.software.product: "FortiProxy"
host.services: (port = "443" and software.vendor: "Fortinet")
host.services.endpoints.http.html_title: "FortiPAM"

 

// scope with: and (host.autonomous_system.asn = <your_ASN> or host.ip: "<your_public_CIDR>")

 

Netlas (tie-breaker only):
http.title:"FortiProxy"
host:<your_public_CIDR> AND port:(443 OR 8443 OR 10443)
certificate.subject.common_name:"<your_domain>"

 

Cross-reference every exposed service and version banner against the CISA Known Exploited Vulnerabilities catalog (https://www.cisa.gov/known-exploited-vulnerabilities-catalog) and against CVE-2026-49975. CVE-2026-49975 is not KEV-listed as of this writing, but the Fortinet edge-appliance family carries multiple historical KEV entries; a KEV match on any other CVE against the same exposed instance is presumptive ransomware and actor-associated initial-access risk and outranks this advisory in remediation priority.

Analysis Queries:

Attribute every hit with at least two independent ownership signals — PTR record, ASN, TLS certificate CN or SAN, WHOIS/RDAP registration, and your own cloud asset inventory — before reporting it as your exposure. Shared hosting and CDN fronting break attribution in both directions; an unattributable hit is inconclusive, not negative.

Version-triage each banner against the affected ranges in Section 1 and record the index scan timestamp alongside the finding. A banner is stale in both directions: an in-range version is a prompt to verify current state on the device, and a fixed-version banner is not evidence of safety if the scan predates your patch window.

Corroborate in at least two of Shodan, Censys, and Netlas before escalating. Absence from an index is evidence only that the index did not observe the host.

Historical pivot: establish whether the exposure spans June 8, 2026 through the present. Continuous exposure across that window means the instance was reachable for the entire period in which public exploit code existed, and should be treated as presumptively enumerated.

Pivot into the internal hypotheses: feed every confirmed-owned, in-range instance into Hypotheses 2 and 3 as a priority target and hunt it exhaustively rather than by sampling. Where the instance fronts or reaches OT workloads, escalate to the OT analysis in Hypothesis 3 and document the blast radius — internet reachability of infrastructure serving a control environment is a finding in its own right under IEC 62443 and NERC CIP, independent of any confirmed exploitation.

Favicon-hash pivoting is available for stripped banners and non-standard ports, but derive the hash from an instance you own by reusing an index-computed value — never by fetching /favicon.ico from a target, and never by copying a hash from a third-party write-up.

API credentials are referenced by environment-variable name only: SHODAN_API_KEY, CENSYS_API_SECRET, CENSYS_ORG_ID, NETLAS_API_KEY. Never inline a key into a query, script, plan, or shell command. A missing key is non-blocking — skip that engine and record the coverage gap.

Hypothesis 2: an unauthenticated remote actor has sent crafted HTTP/2 header frames to an affected Fortinet appliance or Apache HTTP Server instance to force excessive memory allocation, observable as anomalous HTTP/2 connection patterns in network telemetry and as memory-pressure or process-restart events in appliance and host logs.

MITRE ATT&CK: Initial Access | T1190 Exploit Public-Facing Application — the vulnerable listener is reached directly over the network · Impact | T1499 Endpoint Denial of Service and T1499.004 Application or System Exploitation — the technique triggers a resource-exhaustion flaw rather than flooding bandwidth · ICS | T0814 Denial of Service — where the affected appliance mediates control-network traffic.

Collection Queries:

CrowdStrike Falcon LogScale (CQL) — enumerate which third-party data connectors are populating before writing any appliance query, so a zero result is interpretable:

*
| groupBy([#type], function=count(as=events), limit=max)
| sort(events, order=desc, limit=max)

 

#type=/fortinet/i
| groupBy([#type, observer.hostname], function=count(as=events), limit=max)
| sort(events, order=desc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — inbound sessions to appliance management and proxy listeners from non-RFC1918 sources. Fortinet syslog fields that have no ECS equivalent are preserved under the Vendor. prefix by the CrowdStrike Parsing Standard; probe one event with head(1) and fall back to Vendor.* wherever the ECS field comes back null:

#type=/fortinet/i
| in(destination.port, values=[443, 8443, 10443])
| !cidr(source.ip, subnet=["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16","127.0.0.0/8"])
| groupBy([source.ip, destination.ip, destination.port, observer.hostname], function=count(as=sessions), limit=max)
| sort(sessions, order=desc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — endpoint-side view of client connections to the proxy and management listeners, bucketed hourly so an availability cliff is visible as a drop rather than as an absence:

#event_simpleName = "NetworkConnectIP4"
| in(RemotePort, values=[443, 8080, 8443, 3128])
| formatTime(format="%Y-%m-%dT%H:00", as=hourBucket)
| groupBy([hourBucket, RemoteAddressIP4, RemotePort], function=count(as=conns), limit=max)
| sort(hourBucket, order=asc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — external inbound connections accepted by Apache worker processes on managed Linux and Windows hosts. The join runs in reverse direction (ProcessRollup2 as the main query, the heavily filtered network event as the subquery) to stay under the 100,000-row subquery cap:

#event_simpleName = "ProcessRollup2"
| ImageFileName = /(\/httpd|\/apache2)$/i
| join({
#event_simpleName = "NetworkReceiveAcceptIP4"
| in(LocalPort, values=[80, 443, 8080, 8443])
| !cidr(RemoteAddressIP4, subnet=["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16","127.0.0.0/8"])
},
field=[aid, TargetProcessId], key=[aid, ContextProcessId], mode=inner)
| groupBy([ComputerName, ImageFileName, UserName], function=count(as=hits), limit=max)
| sort(hits, order=desc, limit=max)

 

BPF packet capture — capture the HTTP/2 cleartext connection preface and TLS client hellos arriving at the appliance-facing interface. Note that -G requires a strftime format in the -w filename or each rotation overwrites the previous file:

tcpdump -i eth0 -s 0 -w /captures/http2_%Y%m%d_%H%M%S.pcap -G 3600 -C 500 \
'tcp and (port 80 or port 443 or port 8443 or port 10443 or port 8080)'

 

tcpdump -i eth0 -s 0 -w /captures/h2_preface_%Y%m%d_%H%M%S.pcap -G 900 \
'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x50524920'

 

tcpdump -i eth0 -nn -c 100000 -w /captures/h2_syn_rate_%Y%m%d_%H%M%S.pcap -G 300 \
'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack = 0 and dst host <appliance_ip>'

 

Datadog Log Search — appliance syslog forwarded to Datadog, and the equivalent host-side view. Reserved attributes (source, host, service, status, message) are queried without the @ prefix; all others require it:

source:fortinet @destination.port:(443 OR 8443 OR 10443) -@source.ip:10.* -@source.ip:172.16.* -@source.ip:192.168.*
// time range: 2026-06-08T00:00Z to current

 

source:windows @evt.id:(7031 OR 7034 OR 1000 OR 2004)
// time range: 2026-06-08T00:00Z to current

 

source:kubernetes @kubernetes.container_name:"httpd" status:error message:"h2"
// time range: 2026-06-08T00:00Z to current

 

Datadog Live Process Monitoring — Infrastructure > Processes, not a log search; requires the Agent with process_config.process_collection.enabled set to true:

command:httpd user:www-data

 

command:apache2

 

// Fallback where Live Process Monitoring is not enabled:
// source:kubernetes @kubernetes.container_name:"httpd" — Analytics: Table view, group by @kubernetes.pod_name

 

Datadog CloudTrail — where the affected appliance or an Apache workload runs in AWS, capture the security-group and load-balancer changes that would have opened or closed the listener:

source:cloudtrail @evt.name:(AuthorizeSecurityGroupIngress OR RevokeSecurityGroupIngress OR ModifyListener OR CreateLoadBalancer) -@network.client.ip:10.*
// time range: 2026-06-08T00:00Z to current

 

Windows Event IDs to collect — for Windows hosts running Apache HTTP Server, and for management workstations that lost their brokered path through FortiPAM:

Event ID 7031 — Service Control Manager: service terminated unexpectedly and was restarted
Event ID 7034 — Service Control Manager: service terminated unexpectedly (no recovery action)
Event ID 7036 — Service Control Manager: service entered the running or stopped state
Event ID 1000 — Application Error: faulting application, module, and exception code
Event ID 1001 — Windows Error Reporting: fault bucket for the crashed process
Event ID 2004 — Resource-Exhaustion-Detector: Windows diagnosed low virtual memory and named the top consumers
Event ID 2019 — Srv: server unable to allocate from the system nonpaged pool

 

Get-WinEvent -FilterHashtable @{LogName='System'; Id=7031,7034,7036; StartTime=(Get-Date).AddDays(-90)} |
Where-Object { $_.Message -match 'Apache|httpd' } |
Select-Object TimeCreated, Id, MachineName, Message |
Export-Csv -NoTypeInformation -Path .\apache_service_terminations.csv

 

Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Resource-Exhaustion-Detector/Operational'; Id=2004; StartTime=(Get-Date).AddDays(-90)} |
Select-Object TimeCreated, MachineName, Message |
Export-Csv -NoTypeInformation -Path .\resource_exhaustion_events.csv

 

Get-WinEvent -FilterHashtable @{LogName='Application'; Id=1000,1001; StartTime=(Get-Date).AddDays(-90)} |
Where-Object { $_.Message -match 'httpd\.exe|apache' } |
Select-Object TimeCreated, Id, MachineName, Message |
Export-Csv -NoTypeInformation -Path .\apache_application_faults.csv

 

YARA file-system scan — search staging hosts, jump servers, and any host that an actor may have used to launch the attack from inside the estate for HTTP/2 Bomb exploit tooling:

yara -r /opt/hunt/rules/http2_bomb.yar /home/ >> /opt/hunt/out/http2_disk_hits.txt

 

yara -r /opt/hunt/rules/http2_bomb.yar /tmp/ /var/tmp/ /opt/ >> /opt/hunt/out/http2_disk_hits.txt

 

Analysis Queries:

CrowdStrike Falcon LogScale (CQL) — rate analysis. A source producing a large number of short-lived sessions to a management listener within a single minute is the amplification signature; the technique does not require volumetric bandwidth, so look for connection count rather than byte count:

#type=/fortinet/i
| in(destination.port, values=[443, 8443, 10443])
| formatTime(format="%Y-%m-%dT%H:%M", as=minuteBucket)
| groupBy([minuteBucket, source.ip, destination.ip], function=count(as=sessions), limit=max)
| sessions > 200
| sort(sessions, order=desc, limit=max)

 

Wireshark display filters — isolate the HTTP/2 exchange and identify the amplification pattern. Wireshark dissects HTTP/2 over TLS only where a keylog file is supplied; over cleartext h2c the frames dissect directly:

http2
http2.type == 1
http2.type == 4
http2.headers.header.length > 1000
http2.flags.end_headers == 0
tcp.flags.syn == 1 && tcp.flags.ack == 0 && tcp.dstport in {443 8443 10443}
http.request.line contains "Upgrade: h2c"

 

tshark -r /captures/http2_capture.pcap -Y "http2.type == 1" -T fields \
-e frame.time -e ip.src -e ip.dst -e tcp.srcport -e http2.streamid -e http2.length

 

tshark -r /captures/http2_capture.pcap -q -z conv,tcp | sort -k7 -n -r | head -50

 

tshark -r /captures/http2_capture.pcap -Y "tcp.flags.syn == 1 && tcp.flags.ack == 0" -T fields \
-e ip.src | sort | uniq -c | sort -rn | head -50

 

Datadog Log Analytics — the same rate question expressed in Datadog. Log Analytics has no SQL ORDER BY or LIMIT; sort order is set in the UI:

source:fortinet @destination.port:(443 OR 8443 OR 10443)
// Use Timeseries view; group by @source.ip; rollup 1 minute; time range: last 90 days
// Equivalent to the CQL groupBy([minuteBucket, source.ip]) rate query above

 

source:fortinet @destination.port:(443 OR 8443 OR 10443)
// Use Top List view; group by @source.ip; time range: last 90 days

 

Datadog Audit Trail — confirm no Datadog-side monitor or log pipeline covering these appliances was disabled or modified during the window, which would explain a data gap rather than an absence of activity:

source:datadog @evt.name:Monitor
// Use Table view; group by @action, @usr.email; time range: last 90 days

 

source:datadog @evt.name:"Access Management"
// Use Table view; group by @action, @usr.email; time range: last 90 days

 

Datadog Monitor definition — one per hypothesis:

Type: Log Alert
Query: source:fortinet @destination.port:(443 OR 8443 OR 10443) -@source.ip:10.* -@source.ip:172.16.* -@source.ip:192.168.*
Evaluation window: last 5 minutes
Alert condition: count > 500 grouped by @source.ip
Message: "ALERT: high-rate external connection burst to a Fortinet management or proxy listener — possible HTTP/2 memory exhaustion attempt against CVE-2026-49975. Verify appliance memory utilization immediately @pagerduty-soc"
Prerequisites: Fortinet appliance syslog forwarded to Datadog with source:fortinet; destination.port and source.ip parsed as attributes
Create via: Monitors > New Monitor > Log Alert OR POST /api/v1/monitors

 

YARA memory scan — where an internal host is suspected of having launched the attack, scan the interpreter process holding the exploit tooling. Classic YARA 4.5 takes the PID as a positional target; there is no -p flag for process scanning, and YARA-X does not support process memory at all:

yara /opt/hunt/rules/http2_bomb.yar 4821

 

for pid in $(pgrep -f 'python|python3|node|go'); do yara /opt/hunt/rules/http2_bomb.yar $pid; done >> /opt/hunt/out/http2_mem_hits.txt

 

CrowdStrike Falcon can execute the same scan remotely — upload the rule file via Real Time Response (RTR) put, then run it with RTR runscript against the target host; alternatively convert the disk-side string set into a Custom IOA for standing coverage.

Hypothesis 3: an affected appliance has already suffered a memory-exhaustion availability failure, observable as memory-pressure and restart events on the device, as a measurable service interruption for the population behind it, and as a corresponding gap in OT visibility where the appliance mediates a control-network boundary.

MITRE ATT&CK: Impact | T1499 Endpoint Denial of Service — the observable is the outcome rather than the delivery · ICS | T0815 Denial of View — operators lose the traffic path that carries process visibility · ICS | T0826 Loss of Availability — the control-network boundary itself becomes unavailable.

Collection Queries:

CrowdStrike Falcon LogScale (CQL) — appliance-side memory, conserve-mode, restart, and watchdog events from forwarded Fortinet syslog:

#type=/fortinet/i
| Vendor.logdesc = /(memory|conserve|out of memory|restart|reboot|watchdog)/i
| groupBy([observer.hostname, Vendor.logid, Vendor.logdesc], function=count(as=hits), limit=max)
| sort(hits, order=desc, limit=max)

 

CrowdStrike Falcon LogScale (CQL) — Apache worker termination churn on managed hosts. EndOfProcess does not carry ImageFileName, so the image name is taken from ProcessRollup2 and the two are joined on the agent ID and process ID pair:

#event_simpleName = "ProcessRollup2"
| ImageFileName = /(\/httpd|\/apache2)$/i
| join(