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 = "2026/06/06"
  3integration = ["azure"]
  4maturity = "production"
  5min_stack_comments = "Elastic ES|QL values aggregation is more performant in 8.16.5 and above."
  6min_stack_version = "8.17.0"
  7updated_date = "2026/06/06"
  8
  9[rule]
 10author = ["Elastic"]
 11description = """
 12Identifies a high count of failed Microsoft Entra ID sign-in attempts as the result of the target user account being
 13locked out. Adversaries may attempt to brute-force user accounts by repeatedly trying to authenticate with incorrect
 14credentials, leading to account lockouts by Entra ID Smart Lockout policies.
 15"""
 16false_positives = [
 17    """
 18    Automated processes that attempt to authenticate using expired credentials or have misconfigured authentication
 19    settings may lead to false positives.
 20    """,
 21]
 22from = "now-60m"
 23interval = "15m"
 24language = "esql"
 25license = "Elastic License v2"
 26name = "Microsoft Entra ID Exccessive Account Lockouts Detected"
 27note = """## Triage and analysis
 28
 29### Investigating Microsoft Entra ID Exccessive Account Lockouts Detected
 30
 31This 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.
 32
 33### Possible investigation steps
 34
 35- Review `user_id_list` and `user_principal_name`: Check if targeted users include high-value accounts such as administrators, service principals, or shared inboxes.
 36- 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.
 37- 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.
 38- Inspect `device_detail_browser` and `user_agent`: Clients like `"Python Requests"` indicate scripted automation rather than legitimate login attempts.
 39- Evaluate `unique_users` vs. `total_attempts`: A high ratio suggests distributed attacks across multiple accounts, characteristic of password spraying.
 40- Correlate `client_app_display_name` and `incoming_token_type`: PowerShell or unattended sign-in clients may be targeted for automation or legacy auth bypass.
 41- 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.
 42- Validate time range (`first_seen`, `last_seen`): Determine whether the attack is a short burst or part of a longer campaign.
 43
 44### False positive analysis
 45
 46- Misconfigured clients, scripts, or services with outdated credentials may inadvertently cause lockouts.
 47- Repeated lockouts from known internal IPs or during credential rotation windows could be benign.
 48- Legacy applications without modern auth support may repeatedly fail and trigger Smart Lockout.
 49- Specific known user agents (e.g., corporate service accounts).
 50- Internal IPs or cloud-hosted automation with expected failure behavior.
 51
 52### Response and remediation
 53
 54- Investigate locked accounts immediately. Confirm if the account was successfully accessed prior to lockout.
 55- Reset credentials for impacted users and enforce MFA before re-enabling accounts.
 56- Block malicious IPs or ASN at the firewall, identity provider, or Conditional Access level.
 57- Audit authentication methods in use, and enforce modern auth (OAuth, SAML) over legacy protocols.
 58- Strengthen Conditional Access policies to reduce exposure from weak locations, apps, or clients.
 59- Conduct credential hygiene audits to assess reuse and rotation for targeted accounts.
 60"""
 61references = [
 62    "https://www.microsoft.com/en-us/security/blog/2025/05/27/new-russia-affiliated-actor-void-blizzard-targets-critical-sectors-for-espionage/",
 63    "https://cloud.hacktricks.xyz/pentesting-cloud/azure-security/az-unauthenticated-enum-and-initial-entry/az-password-spraying",
 64    "https://learn.microsoft.com/en-us/security/operations/incident-response-playbook-password-spray",
 65    "https://www.sprocketsecurity.com/blog/exploring-modern-password-spraying",
 66    "https://learn.microsoft.com/en-us/purview/audit-log-detailed-properties",
 67    "https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes",
 68    "https://github.com/0xZDH/Omnispray",
 69    "https://github.com/0xZDH/o365spray",
 70]
 71risk_score = 73
 72rule_id = "2d6f5332-42ea-11f0-b09a-f661ea17fbcd"
 73severity = "high"
 74tags = [
 75    "Domain: Cloud",
 76    "Domain: Identity",
 77    "Data Source: Azure",
 78    "Data Source: Entra ID",
 79    "Data Source: Entra ID Sign-in Logs",
 80    "Use Case: Identity and Access Audit",
 81    "Use Case: Threat Detection",
 82    "Tactic: Credential Access",
 83    "Resources: Investigation Guide",
 84]
 85timestamp_override = "event.ingested"
 86type = "esql"
 87
 88query = '''
 89FROM logs-azure.signinlogs*
 90
 91| EVAL
 92    time_window = DATE_TRUNC(30 minutes, @timestamp),
 93    user_id = TO_LOWER(azure.signinlogs.properties.user_principal_name),
 94    ip = source.ip,
 95    login_error = azure.signinlogs.result_description,
 96    error_code = azure.signinlogs.properties.status.error_code,
 97    request_type = TO_LOWER(azure.signinlogs.properties.incoming_token_type),
 98    app_name = TO_LOWER(azure.signinlogs.properties.app_display_name),
 99    asn_org = source.`as`.organization.name,
100    country = source.geo.country_name,
101    user_agent = user_agent.original,
102    event_time = @timestamp
103
104| WHERE event.dataset == "azure.signinlogs"
105    AND event.category == "authentication"
106    AND azure.signinlogs.category IN ("NonInteractiveUserSignInLogs", "SignInLogs")
107    AND event.outcome == "failure"
108    AND azure.signinlogs.properties.authentication_requirement == "singleFactorAuthentication"
109    AND error_code == 50053
110    AND user_id IS NOT NULL AND user_id != ""
111    AND asn_org != "MICROSOFT-CORP-MSN-AS-BLOCK"
112
113| STATS
114    authentication_requirement = VALUES(azure.signinlogs.properties.authentication_requirement),
115    client_app_id = VALUES(azure.signinlogs.properties.app_id),
116    client_app_display_name = VALUES(azure.signinlogs.properties.app_display_name),
117    target_resource_id = VALUES(azure.signinlogs.properties.resource_id),
118    target_resource_display_name = VALUES(azure.signinlogs.properties.resource_display_name),
119    conditional_access_status = VALUES(azure.signinlogs.properties.conditional_access_status),
120    device_detail_browser = VALUES(azure.signinlogs.properties.device_detail.browser),
121    device_detail_device_id = VALUES(azure.signinlogs.properties.device_detail.device_id),
122    device_detail_operating_system = VALUES(azure.signinlogs.properties.device_detail.operating_system),
123    incoming_token_type = VALUES(azure.signinlogs.properties.incoming_token_type),
124    risk_state = VALUES(azure.signinlogs.properties.risk_state),
125    session_id = VALUES(azure.signinlogs.properties.session_id),
126    user_id = VALUES(azure.signinlogs.properties.user_id),
127    user_principal_name = VALUES(azure.signinlogs.properties.user_principal_name),
128    result_description = VALUES(azure.signinlogs.result_description),
129    result_signature = VALUES(azure.signinlogs.result_signature),
130    result_type = VALUES(azure.signinlogs.result_type),
131
132    unique_users = COUNT_DISTINCT(user_id),
133    user_id_list = VALUES(user_id),
134    login_errors = VALUES(login_error),
135    unique_login_errors = COUNT_DISTINCT(login_error),
136    error_codes = VALUES(error_code),
137    unique_error_codes = COUNT_DISTINCT(error_code),
138    request_types = VALUES(request_type),
139    app_names = VALUES(app_name),
140    ip_list = VALUES(ip),
141    unique_ips = COUNT_DISTINCT(ip),
142    source_orgs = VALUES(asn_org),
143    countries = VALUES(country),
144    unique_country_count = COUNT_DISTINCT(country),
145    unique_asn_orgs = COUNT_DISTINCT(asn_org),
146    first_seen = MIN(event_time),
147    last_seen = MAX(event_time),
148    total_attempts = COUNT()
149BY time_window
150| WHERE unique_users >= 15 AND total_attempts >= 20
151| KEEP
152    time_window, total_attempts, first_seen, last_seen,
153    unique_users, user_id_list, login_errors, unique_login_errors,
154    unique_error_codes, error_codes, request_types, app_names,
155    ip_list, unique_ips, source_orgs, countries,
156    unique_country_count, unique_asn_orgs,
157    authentication_requirement, client_app_id, client_app_display_name,
158    target_resource_id, target_resource_display_name, conditional_access_status,
159    device_detail_browser, device_detail_device_id, device_detail_operating_system,
160    incoming_token_type, risk_state, session_id, user_id,
161    user_principal_name, result_description, result_signature, result_type
162'''
163
164
165[[rule.threat]]
166framework = "MITRE ATT&CK"
167[[rule.threat.technique]]
168id = "T1110"
169name = "Brute Force"
170reference = "https://attack.mitre.org/techniques/T1110/"
171[[rule.threat.technique.subtechnique]]
172id = "T1110.001"
173name = "Password Guessing"
174reference = "https://attack.mitre.org/techniques/T1110/001/"
175
176[[rule.threat.technique.subtechnique]]
177id = "T1110.003"
178name = "Password Spraying"
179reference = "https://attack.mitre.org/techniques/T1110/003/"
180
181[[rule.threat.technique.subtechnique]]
182id = "T1110.004"
183name = "Credential Stuffing"
184reference = "https://attack.mitre.org/techniques/T1110/004/"
185
186
187
188[rule.threat.tactic]
189id = "TA0006"
190name = "Credential Access"
191reference = "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