Microsoft Entra ID Exccessive Account Lockouts Detected

Identifies a high count of failed Microsoft Entra ID sign-in attempts as the result of the target user account being locked out. Adversaries may attempt to brute-force user accounts by repeatedly trying to authenticate with incorrect credentials, leading to account lockouts by Entra ID Smart Lockout policies.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2025/07/01"
  3integration = ["azure"]
  4maturity = "production"
  5updated_date = "2025/07/16"
  6
  7[rule]
  8author = ["Elastic"]
  9description = """
 10Identifies a high count of failed Microsoft Entra ID sign-in attempts as the result of the target user account being
 11locked out. Adversaries may attempt to brute-force user accounts by repeatedly trying to authenticate with incorrect
 12credentials, leading to account lockouts by Entra ID Smart Lockout policies.
 13"""
 14false_positives = [
 15    """
 16    Automated processes that attempt to authenticate using expired credentials or have misconfigured authentication
 17    settings may lead to false positives.
 18    """,
 19]
 20from = "now-60m"
 21interval = "15m"
 22language = "esql"
 23license = "Elastic License v2"
 24name = "Microsoft Entra ID Exccessive Account Lockouts Detected"
 25note = """## Triage and analysis
 26
 27### Investigating Microsoft Entra ID Exccessive Account Lockouts Detected
 28
 29This rule detects a high number of sign-in failures due to account lockouts (error code `50053`) in Microsoft Entra ID sign-in logs. These lockouts are typically caused by repeated authentication failures, often as a result of brute-force tactics such as password spraying, credential stuffing, or automated guessing. This detection is time-bucketed and aggregates attempts to identify bursts or coordinated campaigns targeting multiple users.
 30
 31### Possible investigation steps
 32
 33- Review `user_id_list` and `user_principal_name`: Check if targeted users include high-value accounts such as administrators, service principals, or shared inboxes.
 34- Check `error_codes` and `result_description`: Validate that `50053` (account locked) is the consistent failure type. Messages indicating "malicious IP" activity suggest Microsoft’s backend flagged the source.
 35- Analyze `ip_list` and `source_orgs`: Identify whether the activity originated from known malicious infrastructure (e.g., VPNs, botnets, or public cloud providers). In the example, traffic originates from `MASSCOM`, which should be validated.
 36- Inspect `device_detail_browser` and `user_agent`: Clients like `"Python Requests"` indicate scripted automation rather than legitimate login attempts.
 37- Evaluate `unique_users` vs. `total_attempts`: A high ratio suggests distributed attacks across multiple accounts, characteristic of password spraying.
 38- Correlate `client_app_display_name` and `incoming_token_type`: PowerShell or unattended sign-in clients may be targeted for automation or legacy auth bypass.
 39- Review `conditional_access_status` and `risk_state`: If Conditional Access was not applied and risk was not flagged, policy scope or coverage should be reviewed.
 40- Validate time range (`first_seen`, `last_seen`): Determine whether the attack is a short burst or part of a longer campaign.
 41
 42### False positive analysis
 43
 44- Misconfigured clients, scripts, or services with outdated credentials may inadvertently cause lockouts.
 45- Repeated lockouts from known internal IPs or during credential rotation windows could be benign.
 46- Legacy applications without modern auth support may repeatedly fail and trigger Smart Lockout.
 47- Specific known user agents (e.g., corporate service accounts).
 48- Internal IPs or cloud-hosted automation with expected failure behavior.
 49
 50### Response and remediation
 51
 52- Investigate locked accounts immediately. Confirm if the account was successfully accessed prior to lockout.
 53- Reset credentials for impacted users and enforce MFA before re-enabling accounts.
 54- Block malicious IPs or ASN at the firewall, identity provider, or Conditional Access level.
 55- Audit authentication methods in use, and enforce modern auth (OAuth, SAML) over legacy protocols.
 56- Strengthen Conditional Access policies to reduce exposure from weak locations, apps, or clients.
 57- Conduct credential hygiene audits to assess reuse and rotation for targeted accounts.
 58"""
 59references = [
 60    "https://www.microsoft.com/en-us/security/blog/2025/05/27/new-russia-affiliated-actor-void-blizzard-targets-critical-sectors-for-espionage/",
 61    "https://cloud.hacktricks.xyz/pentesting-cloud/azure-security/az-unauthenticated-enum-and-initial-entry/az-password-spraying",
 62    "https://learn.microsoft.com/en-us/security/operations/incident-response-playbook-password-spray",
 63    "https://www.sprocketsecurity.com/blog/exploring-modern-password-spraying",
 64    "https://learn.microsoft.com/en-us/purview/audit-log-detailed-properties",
 65    "https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes",
 66    "https://github.com/0xZDH/Omnispray",
 67    "https://github.com/0xZDH/o365spray",
 68]
 69risk_score = 73
 70rule_id = "2d6f5332-42ea-11f0-b09a-f661ea17fbcd"
 71severity = "high"
 72tags = [
 73    "Domain: Cloud",
 74    "Domain: Identity",
 75    "Data Source: Azure",
 76    "Data Source: Entra ID",
 77    "Data Source: Entra ID Sign-in Logs",
 78    "Use Case: Identity and Access Audit",
 79    "Use Case: Threat Detection",
 80    "Tactic: Credential Access",
 81    "Resources: Investigation Guide",
 82]
 83timestamp_override = "event.ingested"
 84type = "esql"
 85
 86query = '''
 87from logs-azure.signinlogs*
 88
 89| eval
 90    Esql.time_window_date_trunc = date_trunc(30 minutes, @timestamp),
 91    Esql_priv.azure_signinlogs_properties_user_principal_name_lower = to_lower(azure.signinlogs.properties.user_principal_name),
 92    Esql.azure_signinlogs_properties_incoming_token_type_lower = to_lower(azure.signinlogs.properties.incoming_token_type),
 93    Esql.azure_signinlogs_properties_app_display_name_lower = to_lower(azure.signinlogs.properties.app_display_name)
 94
 95| where event.dataset == "azure.signinlogs"
 96    and event.category == "authentication"
 97    and azure.signinlogs.category in ("NonInteractiveUserSignInLogs", "SignInLogs")
 98    and event.outcome == "failure"
 99    and azure.signinlogs.properties.authentication_requirement == "singleFactorAuthentication"
100    and azure.signinlogs.properties.status.error_code == 50053
101    and azure.signinlogs.properties.user_principal_name is not null
102    and azure.signinlogs.properties.user_principal_name != ""
103    and source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK"
104
105| stats
106    Esql.azure_signinlogs_properties_authentication_requirement_values = values(azure.signinlogs.properties.authentication_requirement),
107    Esql.azure_signinlogs_properties_app_id_values = values(azure.signinlogs.properties.app_id),
108    Esql.azure_signinlogs_properties_app_display_name_values = values(azure.signinlogs.properties.app_display_name),
109    Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id),
110    Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name),
111    Esql.azure_signinlogs_properties_conditional_access_status_values = values(azure.signinlogs.properties.conditional_access_status),
112    Esql.azure_signinlogs_properties_device_detail_browser_values = values(azure.signinlogs.properties.device_detail.browser),
113    Esql.azure_signinlogs_properties_device_detail_device_id_values = values(azure.signinlogs.properties.device_detail.device_id),
114    Esql.azure_signinlogs_properties_device_detail_operating_system_values = values(azure.signinlogs.properties.device_detail.operating_system),
115    Esql.azure_signinlogs_properties_incoming_token_type_values = values(azure.signinlogs.properties.incoming_token_type),
116    Esql.azure_signinlogs_properties_risk_state_values = values(azure.signinlogs.properties.risk_state),
117    Esql.azure_signinlogs_properties_session_id_values = values(azure.signinlogs.properties.session_id),
118    Esql.azure_signinlogs_properties_user_id_values = values(azure.signinlogs.properties.user_id),
119    Esql_priv.azure_signinlogs_properties_user_principal_name_values = values(azure.signinlogs.properties.user_principal_name),
120    Esql.azure_signinlogs_result_description_values = values(azure.signinlogs.result_description),
121    Esql.azure_signinlogs_result_signature_values = values(azure.signinlogs.result_signature),
122    Esql.azure_signinlogs_result_type_values = values(azure.signinlogs.result_type),
123
124    Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct = count_distinct(Esql_priv.azure_signinlogs_properties_user_principal_name_lower),
125    Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values = values(Esql_priv.azure_signinlogs_properties_user_principal_name_lower),
126    Esql.azure_signinlogs_result_description_count_distinct = count_distinct(azure.signinlogs.result_description),
127    Esql.azure_signinlogs_properties_status_error_code_count_distinct = count_distinct(azure.signinlogs.properties.status.error_code),
128    Esql.azure_signinlogs_properties_status_error_code_values = values(azure.signinlogs.properties.status.error_code),
129    Esql.azure_signinlogs_properties_incoming_token_type_lower_values = values(Esql.azure_signinlogs_properties_incoming_token_type_lower),
130    Esql.azure_signinlogs_properties_app_display_name_lower_values = values(Esql.azure_signinlogs_properties_app_display_name_lower),
131    Esql.source_ip_values = values(source.ip),
132    Esql.source_ip_count_distinct = count_distinct(source.ip),
133    Esql.source_as_organization_name_values = values(source.`as`.organization.name),
134    Esql.source_as_organization_name_count_distinct = count_distinct(source.`as`.organization.name),
135    Esql.source_geo_country_name_values = values(source.geo.country_name),
136    Esql.source_geo_country_name_count_distinct = count_distinct(source.geo.country_name),
137    Esql.@timestamp.min = min(@timestamp),
138    Esql.@timestamp.max = max(@timestamp),
139    Esql.event_count = count()
140by Esql.time_window_date_trunc
141
142| where Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct >= 15 and Esql.event_count >= 20
143
144| keep
145    Esql.time_window_date_trunc,
146    Esql.event_count,
147    Esql.@timestamp.min,
148    Esql.@timestamp.max,
149    Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct,
150    Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values,
151    Esql.azure_signinlogs_result_description_count_distinct,
152    Esql.azure_signinlogs_result_description_values,
153    Esql.azure_signinlogs_properties_status_error_code_count_distinct,
154    Esql.azure_signinlogs_properties_status_error_code_values,
155    Esql.azure_signinlogs_properties_incoming_token_type_lower_values,
156    Esql.azure_signinlogs_properties_app_display_name_lower_values,
157    Esql.source_ip_values,
158    Esql.source_ip_count_distinct,
159    Esql.source_as_organization_name_values,
160    Esql.source_as_organization_name_count_distinct,
161    Esql.source_geo_country_name_values,
162    Esql.source_geo_country_name_count_distinct,
163    Esql.azure_signinlogs_properties_authentication_requirement_values,
164    Esql.azure_signinlogs_properties_app_id_values,
165    Esql.azure_signinlogs_properties_app_display_name_values,
166    Esql.azure_signinlogs_properties_resource_id_values,
167    Esql.azure_signinlogs_properties_resource_display_name_values,
168    Esql.azure_signinlogs_properties_conditional_access_status_values,
169    Esql.azure_signinlogs_properties_device_detail_browser_values,
170    Esql.azure_signinlogs_properties_device_detail_device_id_values,
171    Esql.azure_signinlogs_properties_device_detail_operating_system_values,
172    Esql.azure_signinlogs_properties_incoming_token_type_values,
173    Esql.azure_signinlogs_properties_risk_state_values,
174    Esql.azure_signinlogs_properties_session_id_values,
175    Esql.azure_signinlogs_properties_user_id_values,
176    Esql_priv.azure_signinlogs_properties_user_principal_name_values,
177    Esql.azure_signinlogs_result_description_values,
178    Esql.azure_signinlogs_result_signature_values,
179    Esql.azure_signinlogs_result_type_values
180'''
181
182
183[[rule.threat]]
184framework = "MITRE ATT&CK"
185[[rule.threat.technique]]
186id = "T1110"
187name = "Brute Force"
188reference = "https://attack.mitre.org/techniques/T1110/"
189[[rule.threat.technique.subtechnique]]
190id = "T1110.001"
191name = "Password Guessing"
192reference = "https://attack.mitre.org/techniques/T1110/001/"
193
194[[rule.threat.technique.subtechnique]]
195id = "T1110.003"
196name = "Password Spraying"
197reference = "https://attack.mitre.org/techniques/T1110/003/"
198
199[[rule.threat.technique.subtechnique]]
200id = "T1110.004"
201name = "Credential Stuffing"
202reference = "https://attack.mitre.org/techniques/T1110/004/"
203
204
205
206[rule.threat.tactic]
207id = "TA0006"
208name = "Credential Access"
209reference = "https://attack.mitre.org/tactics/TA0006/"

Triage and analysis

Investigating Microsoft Entra ID Exccessive Account Lockouts Detected

This rule detects a high number of sign-in failures due to account lockouts (error code 50053) in Microsoft Entra ID sign-in logs. These lockouts are typically caused by repeated authentication failures, often as a result of brute-force tactics such as password spraying, credential stuffing, or automated guessing. This detection is time-bucketed and aggregates attempts to identify bursts or coordinated campaigns targeting multiple users.

Possible investigation steps

  • Review user_id_list and user_principal_name: Check if targeted users include high-value accounts such as administrators, service principals, or shared inboxes.
  • Check error_codes and result_description: Validate that 50053 (account locked) is the consistent failure type. Messages indicating "malicious IP" activity suggest Microsoft’s backend flagged the source.
  • Analyze ip_list and source_orgs: Identify whether the activity originated from known malicious infrastructure (e.g., VPNs, botnets, or public cloud providers). In the example, traffic originates from MASSCOM, which should be validated.
  • Inspect device_detail_browser and user_agent: Clients like "Python Requests" indicate scripted automation rather than legitimate login attempts.
  • Evaluate unique_users vs. total_attempts: A high ratio suggests distributed attacks across multiple accounts, characteristic of password spraying.
  • Correlate client_app_display_name and incoming_token_type: PowerShell or unattended sign-in clients may be targeted for automation or legacy auth bypass.
  • Review conditional_access_status and risk_state: If Conditional Access was not applied and risk was not flagged, policy scope or coverage should be reviewed.
  • Validate time range (first_seen, last_seen): Determine whether the attack is a short burst or part of a longer campaign.

False positive analysis

  • Misconfigured clients, scripts, or services with outdated credentials may inadvertently cause lockouts.
  • Repeated lockouts from known internal IPs or during credential rotation windows could be benign.
  • Legacy applications without modern auth support may repeatedly fail and trigger Smart Lockout.
  • Specific known user agents (e.g., corporate service accounts).
  • Internal IPs or cloud-hosted automation with expected failure behavior.

Response and remediation

  • Investigate locked accounts immediately. Confirm if the account was successfully accessed prior to lockout.
  • Reset credentials for impacted users and enforce MFA before re-enabling accounts.
  • Block malicious IPs or ASN at the firewall, identity provider, or Conditional Access level.
  • Audit authentication methods in use, and enforce modern auth (OAuth, SAML) over legacy protocols.
  • Strengthen Conditional Access policies to reduce exposure from weak locations, apps, or clients.
  • Conduct credential hygiene audits to assess reuse and rotation for targeted accounts.

References

Related rules

to-top