Multiple Microsoft 365 User Account Lockouts in Short Time Window
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 = "2025/07/16"
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"
14language = "esql"
15license = "Elastic License v2"
16name = "Multiple Microsoft 365 User Account Lockouts in Short Time Window"
17note = """## Triage and Analysis
18
19### Investigating Multiple Microsoft 365 User Account Lockouts in Short Time Window
20
21Detects 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.
22
23This 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.
24
25### Investigation Steps
26
27- Review the `user_id_list`: Are specific naming patterns targeted (e.g., admin, helpdesk)?
28- Examine `ip_list` and `source_orgs`: Look for suspicious ISPs or hosting providers.
29- Check `duration_seconds`: A very short window with a high lockout rate often indicates automation.
30- Confirm lockout policy thresholds with IAM or Entra ID admins. Did the policy trigger correctly?
31- Use the `first_seen` and `last_seen` values to pivot into related authentication or audit logs.
32- Correlate with any recent detection of password spraying or credential stuffing activity.
33- Review the `request_type` field to identify which authentication methods were used (e.g., OAuth, SAML, etc.).
34- Check for any successful logins from the same IP or ASN after the lockouts.
35
36### False Positive Analysis
37
38- Automated systems with stale credentials may cause repeated failed logins.
39- Legitimate bulk provisioning or scripted tests could unintentionally cause account lockouts.
40- Red team exercises or penetration tests may resemble the same lockout pattern.
41- Some organizations may have a high volume of lockouts due to user behavior or legacy systems.
42
43### Response Recommendations
44
45- Notify affected users and confirm whether activity was expected or suspicious.
46- Lock or reset credentials for impacted accounts.
47- Block the source IP(s) or ASN temporarily using conditional access or firewall rules.
48- Strengthen lockout and retry delay policies if necessary.
49- Review the originating application(s) involved via `request_types`.
50"""
51references = [
52 "https://learn.microsoft.com/en-us/security/operations/incident-response-playbook-password-spray",
53 "https://learn.microsoft.com/en-us/purview/audit-log-detailed-properties",
54 "https://securityscorecard.com/research/massive-botnet-targets-m365-with-stealthy-password-spraying-attacks/",
55 "https://github.com/0xZDH/Omnispray",
56 "https://github.com/0xZDH/o365spray",
57]
58risk_score = 47
59rule_id = "de67f85e-2d43-11f0-b8c9-f661ea17fbcc"
60severity = "medium"
61tags = [
62 "Domain: Cloud",
63 "Domain: SaaS",
64 "Data Source: Microsoft 365",
65 "Data Source: Microsoft 365 Audit Logs",
66 "Use Case: Threat Detection",
67 "Use Case: Identity and Access Audit",
68 "Tactic: Credential Access",
69 "Resources: Investigation Guide",
70]
71timestamp_override = "event.ingested"
72type = "esql"
73
74query = '''
75from logs-o365.audit-*
76| mv_expand event.category
77| eval
78 Esql.time_window_date_trunc = date_trunc(5 minutes, @timestamp)
79| where
80 event.dataset == "o365.audit" and
81 event.category == "authentication" and
82 event.provider in ("AzureActiveDirectory", "Exchange") and
83 event.action in ("UserLoginFailed", "PasswordLogonInitialAuthUsingPassword") and
84 to_lower(o365.audit.ExtendedProperties.RequestType) rlike "(oauth.*||.*login.*)" and
85 o365.audit.LogonError == "IdsLocked" and
86 to_lower(o365.audit.UserId) != "not available" and
87 o365.audit.Target.Type in ("0", "2", "6", "10") and
88 source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK"
89| stats
90 Esql_priv.o365_audit_UserId_count_distinct = count_distinct(to_lower(o365.audit.UserId)),
91 Esql_priv.o365_audit_UserId_values = values(to_lower(o365.audit.UserId)),
92 Esql.source_ip_values = values(source.ip),
93 Esql.source_ip_count_distinct = count_distinct(source.ip),
94 Esql.source_as_organization_name_values = values(source.`as`.organization.name),
95 Esql.source_as_organization_name_count_distinct = count_distinct(source.`as`.organization.name),
96 Esql.source_geo_country_name_values = values(source.geo.country_name),
97 Esql.source_geo_country_name_count_distinct = count_distinct(source.geo.country_name),
98 Esql.o365_audit_ExtendedProperties_RequestType_values = values(to_lower(o365.audit.ExtendedProperties.RequestType)),
99 Esql.timestamp_first_seen = min(@timestamp),
100 Esql.timestamp_last_seen = max(@timestamp),
101 Esql.event_count = count(*)
102 by Esql.time_window_date_trunc
103| eval
104 Esql.event_duration_seconds = date_diff("seconds", Esql.timestamp_first_seen, Esql.timestamp_last_seen)
105| keep
106 Esql.time_window_date_trunc,
107 Esql_priv.o365_audit_UserId_count_distinct,
108 Esql_priv.o365_audit_UserId_values,
109 Esql.source_ip_values,
110 Esql.source_ip_count_distinct,
111 Esql.source_as_organization_name_values,
112 Esql.source_as_organization_name_count_distinct,
113 Esql.source_geo_country_name_values,
114 Esql.source_geo_country_name_count_distinct,
115 Esql.o365_audit_ExtendedProperties_RequestType_values,
116 Esql.timestamp_first_seen,
117 Esql.timestamp_last_seen,
118 Esql.event_count,
119 Esql.event_duration_seconds
120| where
121 Esql_priv.o365_audit_UserId_count_distinct >= 10 and
122 Esql.event_count >= 10 and
123 Esql.event_duration_seconds <= 300
124'''
125
126
127[[rule.threat]]
128framework = "MITRE ATT&CK"
129[[rule.threat.technique]]
130id = "T1110"
131name = "Brute Force"
132reference = "https://attack.mitre.org/techniques/T1110/"
133
134
135[rule.threat.tactic]
136id = "TA0006"
137name = "Credential Access"
138reference = "https://attack.mitre.org/tactics/TA0006/"
Triage and Analysis
Investigating Multiple Microsoft 365 User Account Lockouts in Short Time Window
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_list
andsource_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_seen
andlast_seen
values to pivot into related authentication or audit logs. - Correlate with any recent detection of password spraying or credential stuffing activity.
- Review the
request_type
field 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
- Potential Microsoft 365 User Account Brute Force
- Microsoft 365 or Entra ID Sign-in from a Suspicious Source
- Microsoft 365 OAuth Redirect to Device Registration for User Principal
- Deprecated - Azure Entra Sign-in Brute Force Microsoft 365 Accounts by Repeat Source
- Microsoft 365 Brute Force via Entra ID Sign-Ins