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.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2025/05/10"
3integration = ["o365"]
4maturity = "production"
5updated_date = "2026/09/18"
6
7[rule]
8author = ["Elastic"]
9description = """
10Detects a burst of Microsoft 365 user account lockouts within a short 5-minute window. A high number of IdsLocked login
11errors across multiple user accounts may indicate brute-force attempts for the same users resulting in lockouts.
12"""
13from = "now-9m"
14interval = "8m"
15language = "esql"
16license = "Elastic License v2"
17name = "M365 Identity User Account Lockouts"
18note = """## Triage and Analysis
19
20### Investigating M365 Identity User Account Lockouts
21
22Detects 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.
23
24This rule uses ESQL aggregations and thus has dynamically generated fields. Correlation of the values in the alert document may need to be performed to the original sign-in and Graph events for further context.
25
26### Investigation Steps
27
28- Review the `user_id_list`: Are specific naming patterns targeted (e.g., admin, helpdesk)?
29- Examine `ip_list` and `source_orgs`: Look for suspicious ISPs or hosting providers.
30- Check `duration_seconds`: A very short window with a high lockout rate often indicates automation.
31- Confirm lockout policy thresholds with IAM or Entra ID admins. Did the policy trigger correctly?
32- Use the `first_seen` and `last_seen` values to pivot into related authentication or audit logs.
33- Correlate with any recent detection of password spraying or credential stuffing activity.
34- Review the `request_type` field to identify which authentication methods were used (e.g., OAuth, SAML, etc.).
35- Check for any successful logins from the same IP or ASN after the lockouts.
36
37### False Positive Analysis
38
39- Automated systems with stale credentials may cause repeated failed logins.
40- Legitimate bulk provisioning or scripted tests could unintentionally cause account lockouts.
41- Red team exercises or penetration tests may resemble the same lockout pattern.
42- Some organizations may have a high volume of lockouts due to user behavior or legacy systems.
43
44### Response Recommendations
45
46- Notify affected users and confirm whether activity was expected or suspicious.
47- Lock or reset credentials for impacted accounts.
48- Block the source IP(s) or ASN temporarily using conditional access or firewall rules.
49- Strengthen lockout and retry delay policies if necessary.
50- Review the originating application(s) involved via `request_types`.
51"""
52references = [
53 "https://learn.microsoft.com/en-us/security/operations/incident-response-playbook-password-spray",
54 "https://learn.microsoft.com/en-us/purview/audit-log-detailed-properties",
55 "https://securityscorecard.com/research/massive-botnet-targets-m365-with-stealthy-password-spraying-attacks/",
56 "https://github.com/0xZDH/Omnispray",
57 "https://github.com/0xZDH/o365spray",
58]
59risk_score = 47
60rule_id = "de67f85e-2d43-11f0-b8c9-f661ea17fbcc"
61severity = "medium"
62tags = [
63 "Domain: Cloud",
64 "Domain: SaaS",
65 "Data Source: Microsoft 365",
66 "Data Source: Microsoft 365 Audit Logs",
67 "Use Case: Threat Detection",
68 "Use Case: Identity and Access Audit",
69 "Tactic: Credential Access",
70 "Resources: Investigation Guide",
71 "Noise: High",
72 "Performance: Normal",
73 "Profile: Aggressive",
74 "Rule Type: ES|QL",
75 "Platform: Microsoft 365",
76]
77timestamp_override = "event.ingested"
78type = "esql"
79
80query = '''
81from logs-o365.audit-*
82| mv_expand event.category
83| eval
84 Esql.time_window_date_trunc = date_trunc(5 minutes, @timestamp)
85| where
86 data_stream.dataset == "o365.audit" and
87 event.category == "authentication" and
88 event.provider in ("AzureActiveDirectory", "Exchange") and
89 event.action in ("UserLoginFailed", "PasswordLogonInitialAuthUsingPassword") and
90 to_lower(o365.audit.ExtendedProperties.RequestType) rlike "(oauth.*|.*login.*)" and
91 o365.audit.LogonError == "IdsLocked" and
92 to_lower(o365.audit.UserId) != "not available" and
93 o365.audit.Target.Type in ("0", "2", "6", "10") and
94 source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK"
95| stats
96 Esql_priv.o365_audit_UserId_count_distinct = count_distinct(to_lower(o365.audit.UserId)),
97 Esql_priv.o365_audit_UserId_values = values(to_lower(o365.audit.UserId)),
98 Esql.source_ip_values = values(source.ip),
99 Esql.source_ip_count_distinct = count_distinct(source.ip),
100 Esql.source_as_organization_name_values = values(source.`as`.organization.name),
101 Esql.source_as_organization_name_count_distinct = count_distinct(source.`as`.organization.name),
102 Esql.source_geo_country_name_values = values(source.geo.country_name),
103 Esql.source_geo_country_name_count_distinct = count_distinct(source.geo.country_name),
104 Esql.o365_audit_ExtendedProperties_RequestType_values = values(to_lower(o365.audit.ExtendedProperties.RequestType)),
105 Esql.timestamp_first_seen = min(@timestamp),
106 Esql.timestamp_last_seen = max(@timestamp),
107 Esql.event_count = count(*)
108 by Esql.time_window_date_trunc
109| eval
110 Esql.event_duration_seconds = date_diff("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen)
111| keep
112 Esql.time_window_date_trunc,
113 Esql_priv.o365_audit_UserId_count_distinct,
114 Esql_priv.o365_audit_UserId_values,
115 Esql.source_ip_values,
116 Esql.source_ip_count_distinct,
117 Esql.source_as_organization_name_values,
118 Esql.source_as_organization_name_count_distinct,
119 Esql.source_geo_country_name_values,
120 Esql.source_geo_country_name_count_distinct,
121 Esql.o365_audit_ExtendedProperties_RequestType_values,
122 Esql.timestamp_first_seen,
123 Esql.timestamp_last_seen,
124 Esql.event_count,
125 Esql.event_duration_seconds
126| where
127 Esql_priv.o365_audit_UserId_count_distinct >= 10 and
128 Esql.event_count >= 10 and
129 Esql.event_duration_seconds <= 300
130'''
131
132
133[[rule.threat]]
134framework = "MITRE ATT&CK"
135[[rule.threat.technique]]
136id = "T1110"
137name = "Brute Force"
138reference = "https://attack.mitre.org/techniques/T1110/"
139[[rule.threat.technique.subtechnique]]
140id = "T1110.001"
141name = "Password Guessing"
142reference = "https://attack.mitre.org/techniques/T1110/001/"
143
144[[rule.threat.technique.subtechnique]]
145id = "T1110.003"
146name = "Password Spraying"
147reference = "https://attack.mitre.org/techniques/T1110/003/"
148
149[[rule.threat.technique.subtechnique]]
150id = "T1110.004"
151name = "Credential Stuffing"
152reference = "https://attack.mitre.org/techniques/T1110/004/"
153
154
155
156[rule.threat.tactic]
157id = "TA0006"
158name = "Credential Access"
159reference = "https://attack.mitre.org/tactics/TA0006/"
160
161[[rule.threat]]
162framework = "MITRE ATT&CK"
163[[rule.threat.technique]]
164id = "T1078"
165name = "Valid Accounts"
166reference = "https://attack.mitre.org/techniques/T1078/"
167
168[[rule.threat.technique.subtechnique]]
169id = "T1078.004"
170name = "Cloud Accounts"
171reference = "https://attack.mitre.org/techniques/T1078/004/"
172
173[rule.threat.tactic]
174id = "TA0001"
175name = "Initial Access"
176reference = "https://attack.mitre.org/tactics/TA0001/"
Triage and Analysis
Investigating 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.
This rule uses ESQL aggregations and thus has dynamically generated fields. Correlation of the values in the alert document may need to be performed to the original sign-in and Graph events for further context.
Investigation Steps
- Review the
user_id_list: Are specific naming patterns targeted (e.g., admin, helpdesk)? - Examine
ip_listandsource_orgs: Look for suspicious ISPs or hosting providers. - Check
duration_seconds: A very short window with a high lockout rate often indicates automation. - Confirm lockout policy thresholds with IAM or Entra ID admins. Did the policy trigger correctly?
- Use the
first_seenandlast_seenvalues to pivot into related authentication or audit logs. - Correlate with any recent detection of password spraying or credential stuffing activity.
- Review the
request_typefield to identify which authentication methods were used (e.g., OAuth, SAML, etc.). - Check for any successful logins from the same IP or ASN after the lockouts.
False Positive Analysis
- Automated systems with stale credentials may cause repeated failed logins.
- Legitimate bulk provisioning or scripted tests could unintentionally cause account lockouts.
- Red team exercises or penetration tests may resemble the same lockout pattern.
- Some organizations may have a high volume of lockouts due to user behavior or legacy systems.
Response Recommendations
- Notify affected users and confirm whether activity was expected or suspicious.
- Lock or reset credentials for impacted accounts.
- Block the source IP(s) or ASN temporarily using conditional access or firewall rules.
- Strengthen lockout and retry delay policies if necessary.
- Review the originating application(s) involved via
request_types.
References
Related rules
- M365 Identity User Brute Force Attempted
- Entra ID Concurrent Sign-in with Suspicious Properties
- Entra ID Kali365 Default User-Agent Detected
- Entra ID Sign-in Brute Force Attempted (Microsoft 365)
- M365 Exchange Mailbox Items Accessed Excessively