Detection rules › Elastic

Newly Observed FortiGate Alert

Status
production
Severity
critical
Time window
7205m
Group by
event.category, event.outcome, message
Author
Elastic
Source
github.com/elastic/detection-rules

This rule detects FortiGate alerts that are observed for the first time in the previous 5 days of alert history. Analysts can use this to prioritize triage and response.

Rule body

[metadata]
creation_date = "2026/01/21"
integration = ["fortinet_fortigate"]
maturity = "production"
updated_date = "2026/02/18"

[rule]
author = ["Elastic"]
description = """
This rule detects FortiGate alerts that are observed for the first time in the previous 5 days of alert history.
Analysts can use this to prioritize triage and response.
"""
from = "now-7205m"
interval = "5m"
language = "esql"
license = "Elastic License v2"
name = "Newly Observed FortiGate Alert"
risk_score = 99
rule_id = "2c40dfe2-c13e-48a8-8eff-fb9bfb2a7854"
severity = "critical"
tags = ["Use Case: Threat Detection", "Rule Type: Higher-Order Rule", "Resources: Investigation Guide", "Domain: Network", "Data Source: Fortinet"]
timestamp_override = "event.ingested"
type = "esql"

query = '''
FROM logs-fortinet_fortigate.*, filebeat-* metadata _id

| WHERE event.module == "fortinet_fortigate" and event.action in ("signature", "ssl-anomaly") and
  message is not null and event.category != "authentication" and
  message != "Connection Failed" and not message like "Web.Client: *" and
  not message like "Network.Service: *" and not message like "General.Interest*" and not message like "Update: *" and
  not message like "tcp_reassembler*" and not message like "a-ipdf*" and not message like "Video*" and not message like "nbss_decode*" and
  not message like "name_server*" and not message like "misc*" and not message like "Collaboration*" and not message like "Business*" and
  not message like "Cloud.IT*" and not message like "Mobile*"

| STATS Esql.alerts_count = count(*),
        Esql.first_time_seen = MIN(@timestamp),
        Esql.distinct_count_src_ip = COUNT_DISTINCT(source.ip),
        Esql.distinct_count_dst_ip = COUNT_DISTINCT(destination.ip),
        src_ip = VALUES(source.ip),
        dst_ip = VALUES(destination.ip),
        url_domain = VALUES(url.domain),
        url_path = VALUES(url.path) by message, event.category, event.outcome

// first time seen is within 10m of the rule execution time
| eval Esql.recent = DATE_DIFF("minute", Esql.first_time_seen, now())
| where Esql.recent <= 10 and Esql.alerts_count <= 5 and Esql.distinct_count_src_ip <= 2 and Esql.distinct_count_dst_ip <= 2

// move dynamic fields to ECS equivalent for rule exceptions
| eval source.ip = MV_FIRST(src_ip),
       destination.ip = MV_FIRST(dst_ip),
       url.domain = MV_FIRST(url_domain),
       url.path = MV_FIRST(url_path)

| keep message, event.category, event.outcome, Esql.*, source.ip, destination.ip, url.domain, url.path
'''
note = """## Triage and analysis

### Investigating Newly Observed Fortigate Alert

This rule surfaces newly observed, low-frequency high severity FortiGate alerts within the last 5 days.

Because the alert has not been seen previously, it should be prioritized for validation to determine whether it represents a true compromise or rare benign activity.

### Investigation Steps

- Identify the source address, affected host and review the associated message to understand the alert.
- Validate the source address under which the activity occurred and assess whether it aligns with normal behavior.
- Refer to the specific alert details like event.original to get more context.

### False Positive Considerations

- Vulnerability scanners and pentesting.
- Administrative scripts or automation tools can trigger detections when first introduced.
- Development or testing environments may produce one-off behaviors that resemble malicious techniques.

### Response and Remediation

- If the activity is confirmed malicious, isolate the affected host to prevent further execution or lateral movement.
- Terminate malicious processes and remove any dropped files or persistence mechanisms.
- Collect forensic artifacts to understand initial access and execution flow.
- Patch or remediate any vulnerabilities or misconfigurations that enabled the behavior.
- If benign, document the finding and consider tuning or exception handling to reduce future noise.
- Continue monitoring the host and environment for recurrence of the behavior or related alerts."""
references = ["https://www.elastic.co/docs/reference/integrations/fortinet_fortigate"]

Stages and Predicates

Stage 1: from

FROM logs-fortinet_fortigate.*, filebeat-* metadata _id

Stage 2: where

| WHERE event.module == "fortinet_fortigate" and event.action in ("signature", "ssl-anomaly") and
  message is not null and event.category != "authentication" and
  message != "Connection Failed" and not message like "Web.Client: *" and
  not message like "Network.Service: *" and not message like "General.Interest*" and not message like "Update: *" and
  not message like "tcp_reassembler*" and not message like "a-ipdf*" and not message like "Video*" and not message like "nbss_decode*" and
  not message like "name_server*" and not message like "misc*" and not message like "Collaboration*" and not message like "Business*" and
  not message like "Cloud.IT*" and not message like "Mobile*"

Stage 3: stats

| STATS Esql.alerts_count = count(*),
        Esql.first_time_seen = MIN(@timestamp),
        Esql.distinct_count_src_ip = COUNT_DISTINCT(source.ip),
        Esql.distinct_count_dst_ip = COUNT_DISTINCT(destination.ip),
        src_ip = VALUES(source.ip),
        dst_ip = VALUES(destination.ip),
        url_domain = VALUES(url.domain),
        url_path = VALUES(url.path) by message, event.category, event.outcome

Stage 4: eval

| eval Esql.recent = DATE_DIFF("minute", Esql.first_time_seen, now())

Stage 5: where

| where Esql.recent <= 10 and Esql.alerts_count <= 5 and Esql.distinct_count_src_ip <= 2 and Esql.distinct_count_dst_ip <= 2

Stage 6: eval

| eval source.ip = MV_FIRST(src_ip),
       destination.ip = MV_FIRST(dst_ip),
       url.domain = MV_FIRST(url_domain),
       url.path = MV_FIRST(url_path)

Stage 7: keep

| keep message, event.category, event.outcome, Esql.*, source.ip, destination.ip, url.domain, url.path

Exclusions

The rule actively suppresses these predicates.

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
messageKEEP message
event.categoryKEEP event.category
event.outcomeKEEP event.outcome
Esql.*KEEP Esql.*
source.ipKEEP source.ip
destination.ipKEEP destination.ip
url.domainKEEP url.domain
url.pathKEEP url.path