Elastic medium stable kql

M365 Identity User Account Lockouts

Detects a burst of Microsoft 365 user account lockouts within a short 5-minute window. A high number of IdsLocked login errors across multiple user accounts may indicate brute-force attempts for the same users resulting in lockouts.

View Source

Detection Logic

from logs-o365.audit-*
| mv_expand event.category
| eval
    Esql.time_window_date_trunc = date_trunc(5 minutes, @timestamp)
| where
    data_stream.dataset == "o365.audit" and
    event.category == "authentication" and
    event.provider in ("AzureActiveDirectory", "Exchange") and
    event.action in ("UserLoginFailed", "PasswordLogonInitialAuthUsingPassword") and
    to_lower(o365.audit.ExtendedProperties.RequestType) rlike "(oauth.*
| 
| .*login.*)" and
    o365.audit.LogonError == "IdsLocked" and
    to_lower(o365.audit.UserId) != "not available" and
    o365.audit.Target.Type in ("0", "2", "6", "10") and
    source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK"
| stats
    Esql_priv.o365_audit_UserId_count_distinct = count_distinct(to_lower(o365.audit.UserId)),
    Esql_priv.o365_audit_UserId_values = values(to_lower(o365.audit.UserId)),
    Esql.source_ip_values = values(source.ip),
    Esql.source_ip_count_distinct = count_distinct(source.ip),
    Esql.source_as_organization_name_values = values(source.`as`.organization.name),
    Esql.source_as_organization_name_count_distinct = count_distinct(source.`as`.organization.name),
    Esql.source_geo_country_name_values = values(source.geo.country_name),
    Esql.source_geo_country_name_count_distinct = count_distinct(source.geo.country_name),
    Esql.o365_audit_ExtendedProperties_RequestType_values = values(to_lower(o365.audit.ExtendedProperties.RequestType)),
    Esql.timestamp_first_seen = min(@timestamp),
    Esql.timestamp_last_seen = max(@timestamp),
    Esql.event_count = count(*)
  by Esql.time_window_date_trunc
| eval
    Esql.event_duration_seconds = date_diff("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen)
| keep
    Esql.time_window_date_trunc,
    Esql_priv.o365_audit_UserId_count_distinct,
    Esql_priv.o365_audit_UserId_values,
    Esql.source_ip_values,
    Esql.source_ip_count_distinct,
    Esql.source_as_organization_name_values,
    Esql.source_as_organization_name_count_distinct,
    Esql.source_geo_country_name_values,
    Esql.source_geo_country_name_count_distinct,
    Esql.o365_audit_ExtendedProperties_RequestType_values,
    Esql.timestamp_first_seen,
    Esql.timestamp_last_seen,
    Esql.event_count,
    Esql.event_duration_seconds
| where
    Esql_priv.o365_audit_UserId_count_distinct >= 10 and
    Esql.event_count >= 10 and
    Esql.event_duration_seconds <= 300

Field Validations

Loading…

Comments (0)

Loading comments...