Detection rules › Elastic

First-Time FortiGate Administrator Login

Status
production
Severity
high
Time window
7205m
Group by
fortinet.firewall.profile, source.user.name
Author
Elastic
Source
github.com/elastic/detection-rules

This rule detects the first observed successful login of a user with the Administrator role to the FortiGate management interface within the last 5 days. First-time administrator logins can indicate newly provisioned accounts, misconfigurations, or unauthorized access using valid credentials and should be reviewed promptly.

MITRE ATT&CK coverage

TacticTechniques
Initial AccessT1078 Valid Accounts

Rule body elastic

[metadata]
creation_date = "2026/01/28"
integration = ["fortinet_fortigate"]
maturity = "production"
updated_date = "2026/05/06"

[rule]
author = ["Elastic"]
description = """
This rule detects the first observed successful login of a user with the Administrator role to the FortiGate management
interface within the last 5 days. First-time administrator logins can indicate newly provisioned accounts,
misconfigurations, or unauthorized access using valid credentials and should be reviewed promptly.
"""
from = "now-7205m"
interval = "5m"
language = "esql"
license = "Elastic License v2"
name = "First-Time FortiGate Administrator Login"
note = """## Triage and Analysis

### Investigating First-Time FortiGate Administrator Login

This alert indicates that a user with the **Administrator** role has successfully logged in to the FortiGate management interface for the first time within the last 5 days of observed data.

Because administrator access provides full control over network security devices, any newly observed admin login should be validated to confirm it is expected and authorized.

### Investigation Steps

- **Identify the account**
  - Review `source.user.name` and confirm whether the account is known and officially provisioned.
  - Determine whether this is a newly created administrator or an existing account logging in for the first time.

- **Validate the source**
  - Review `source.ip` and confirm whether it originates from a trusted management network, VPN, or jump host.
  - Investigate geolocation or ASN if the source IP is external or unusual.

- **Review login context**
  - Examine associated FortiGate log messages for details such as login method, interface, or authentication source.
  - Check for additional administrative actions following the login (policy changes, user creation, configuration exports).
  - Review  `fortinet.firewall.profile` to identify the FortiGate Admin Profile the identity logged in under.

- **Correlate with recent changes**
  - Verify whether there were recent change requests, onboarding activities, or maintenance windows that explain the login.
  - Look for other authentication attempts (failed or successful) from the same source or user.

### False Positive Considerations

- Newly onboarded administrators or service accounts.
- First-time logins after log retention changes or data source onboarding.
- Automation, backup, or monitoring tools introduced recently.
- Lab, development, or test FortiGate devices.

### Response and Remediation

- **If authorized**
  - Document the activity and consider adding an exception if the behavior is expected.
  - Ensure the account follows least-privilege and MFA best practices.

- **If suspicious or unauthorized**
  - Disable or restrict the administrator account immediately.
  - Rotate credentials and review authentication sources.
  - Audit recent FortiGate configuration changes.
  - Review surrounding network activity for lateral movement or persistence attempts."""
references = [
    "https://www.elastic.co/docs/reference/integrations/fortinet_fortigate",
    "https://www.cisa.gov/news-events/alerts/2026/01/28/fortinet-releases-guidance-address-ongoing-exploitation-authentication-bypass-vulnerability-cve-2026",
]
risk_score = 73
rule_id = "55a372b9-f5b6-4069-a089-8637c00609a2"
severity = "high"
tags = [
    "Use Case: Threat Detection",
    "Tactic: Initial Access",
    "Resources: Investigation Guide",
    "Domain: Network",
    "Domain: Identity",
    "Data Source: Fortinet",
    "Data Source: Fortinet FortiGate",
]
timestamp_override = "event.ingested"
type = "esql"

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

| WHERE data_stream.dataset == "fortinet_fortigate.log" and
        event.category == "authentication" and event.action == "login" and
        event.outcome == "success" and source.user.roles == "Administrator" and source.user.name is not null
| stats Esql.logon_count = count(*),
       Esql.first_time_seen = MIN(@timestamp),
       Esql.source_ip_values = VALUES(source.ip),
       Esql.message_values = VALUES(message) by source.user.name, fortinet.firewall.profile

// first time seen is within 6m of the rule execution time and for the last 5d of events history
| eval Esql.recent = DATE_DIFF("minute", Esql.first_time_seen, now())
| where Esql.recent <= 6 and Esql.logon_count == 1

// move dynamic fields to ECS equivalent for rule exceptions
| eval source.ip = MV_FIRST(Esql.source_ip_values)

| keep source.ip,
       source.user.name,
       fortinet.firewall.profile,
       Esql.logon_count,
       Esql.first_time_seen,
       Esql.source_ip_values,
       Esql.message_values,
       Esql.recent
'''


[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1078"
name = "Valid Accounts"
reference = "https://attack.mitre.org/techniques/T1078/"


[rule.threat.tactic]
id = "TA0001"
name = "Initial Access"
reference = "https://attack.mitre.org/tactics/TA0001/"

Stages and Predicates

Stage 1: from

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

Stage 2: where

| WHERE data_stream.dataset == "fortinet_fortigate.log" and
        event.category == "authentication" and event.action == "login" and
        event.outcome == "success" and source.user.roles == "Administrator" and source.user.name is not null

Stage 3: stats

| stats Esql.logon_count = count(*),
       Esql.first_time_seen = MIN(@timestamp),
       Esql.source_ip_values = VALUES(source.ip),
       Esql.message_values = VALUES(message) by source.user.name, fortinet.firewall.profile

Stage 4: eval

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

Stage 5: where

| where Esql.recent <= 6 and Esql.logon_count == 1

Stage 6: eval

| eval source.ip = MV_FIRST(Esql.source_ip_values)

Stage 7: keep

| keep source.ip,
       source.user.name,
       fortinet.firewall.profile,
       Esql.logon_count,
       Esql.first_time_seen,
       Esql.source_ip_values,
       Esql.message_values,
       Esql.recent

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
Esql.logon_counteq
  • 1
Esql.recentle
  • 6
data_stream.dataseteq
  • fortinet_fortigate.log
event.actioneq
  • login
event.categoryeq
  • authentication
event.outcomeeq
  • success
source.user.nameis_not_null
  • (no value, null check)
source.user.roleseq
  • Administrator

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
source.ipKEEP source.ip
source.user.nameKEEP source.user.name
fortinet.firewall.profileKEEP fortinet.firewall.profile
Esql.logon_countKEEP Esql.logon_count
Esql.first_time_seenKEEP Esql.first_time_seen
Esql.source_ip_valuesKEEP Esql.source_ip_values
Esql.message_valuesKEEP Esql.message_values
Esql.recentKEEP Esql.recent