Microsoft Entra ID Exccessive Account Lockouts Detected
editMicrosoft Entra ID Exccessive Account Lockouts Detected
editIdentifies 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.
Rule type: esql
Rule indices: None
Severity: high
Risk score: 73
Runs every: 15m
Searches indices from: now-60m (Date Math format, see also Additional look-back time
)
Maximum alerts per execution: 100
References:
- https://www.microsoft.com/en-us/security/blog/2025/05/27/new-russia-affiliated-actor-void-blizzard-targets-critical-sectors-for-espionage/
- https://cloud.hacktricks.xyz/pentesting-cloud/azure-security/az-unauthenticated-enum-and-initial-entry/az-password-spraying
- https://learn.microsoft.com/en-us/security/operations/incident-response-playbook-password-spray
- https://www.sprocketsecurity.com/blog/exploring-modern-password-spraying
- https://learn.microsoft.com/en-us/purview/audit-log-detailed-properties
- https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes
- https://github.com/0xZDH/Omnispray
- https://github.com/0xZDH/o365spray
Tags:
- Domain: Cloud
- Domain: Identity
- Data Source: Azure
- Data Source: Entra ID
- Data Source: Entra ID Sign-in Logs
- Use Case: Identity and Access Audit
- Use Case: Threat Detection
- Tactic: Credential Access
- Resources: Investigation Guide
Version: 2
Rule authors:
- Elastic
Rule license: Elastic License v2
Investigation guide
editTriage 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
anduser_principal_name
: Check if targeted users include high-value accounts such as administrators, service principals, or shared inboxes. -
Check
error_codes
andresult_description
: Validate that50053
(account locked) is the consistent failure type. Messages indicating "malicious IP" activity suggest Microsoft’s backend flagged the source. -
Analyze
ip_list
andsource_orgs
: Identify whether the activity originated from known malicious infrastructure (e.g., VPNs, botnets, or public cloud providers). In the example, traffic originates fromMASSCOM
, which should be validated. -
Inspect
device_detail_browser
anduser_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
andincoming_token_type
: PowerShell or unattended sign-in clients may be targeted for automation or legacy auth bypass. -
Review
conditional_access_status
andrisk_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.
Rule query
editfrom logs-azure.signinlogs* | eval Esql.time_window_date_trunc = date_trunc(30 minutes, @timestamp), Esql_priv.azure_signinlogs_properties_user_principal_name_lower = to_lower(azure.signinlogs.properties.user_principal_name), Esql.azure_signinlogs_properties_incoming_token_type_lower = to_lower(azure.signinlogs.properties.incoming_token_type), Esql.azure_signinlogs_properties_app_display_name_lower = to_lower(azure.signinlogs.properties.app_display_name) | where event.dataset == "azure.signinlogs" and event.category == "authentication" and azure.signinlogs.category in ("NonInteractiveUserSignInLogs", "SignInLogs") and event.outcome == "failure" and azure.signinlogs.properties.authentication_requirement == "singleFactorAuthentication" and azure.signinlogs.properties.status.error_code == 50053 and azure.signinlogs.properties.user_principal_name is not null and azure.signinlogs.properties.user_principal_name != "" and source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK" | stats Esql.azure_signinlogs_properties_authentication_requirement_values = values(azure.signinlogs.properties.authentication_requirement), Esql.azure_signinlogs_properties_app_id_values = values(azure.signinlogs.properties.app_id), Esql.azure_signinlogs_properties_app_display_name_values = values(azure.signinlogs.properties.app_display_name), Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id), Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name), Esql.azure_signinlogs_properties_conditional_access_status_values = values(azure.signinlogs.properties.conditional_access_status), Esql.azure_signinlogs_properties_device_detail_browser_values = values(azure.signinlogs.properties.device_detail.browser), Esql.azure_signinlogs_properties_device_detail_device_id_values = values(azure.signinlogs.properties.device_detail.device_id), Esql.azure_signinlogs_properties_device_detail_operating_system_values = values(azure.signinlogs.properties.device_detail.operating_system), Esql.azure_signinlogs_properties_incoming_token_type_values = values(azure.signinlogs.properties.incoming_token_type), Esql.azure_signinlogs_properties_risk_state_values = values(azure.signinlogs.properties.risk_state), Esql.azure_signinlogs_properties_session_id_values = values(azure.signinlogs.properties.session_id), Esql.azure_signinlogs_properties_user_id_values = values(azure.signinlogs.properties.user_id), Esql_priv.azure_signinlogs_properties_user_principal_name_values = values(azure.signinlogs.properties.user_principal_name), Esql.azure_signinlogs_result_description_values = values(azure.signinlogs.result_description), Esql.azure_signinlogs_result_signature_values = values(azure.signinlogs.result_signature), Esql.azure_signinlogs_result_type_values = values(azure.signinlogs.result_type), Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct = count_distinct(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values = values(Esql_priv.azure_signinlogs_properties_user_principal_name_lower), Esql.azure_signinlogs_result_description_count_distinct = count_distinct(azure.signinlogs.result_description), Esql.azure_signinlogs_properties_status_error_code_count_distinct = count_distinct(azure.signinlogs.properties.status.error_code), Esql.azure_signinlogs_properties_status_error_code_values = values(azure.signinlogs.properties.status.error_code), Esql.azure_signinlogs_properties_incoming_token_type_lower_values = values(Esql.azure_signinlogs_properties_incoming_token_type_lower), Esql.azure_signinlogs_properties_app_display_name_lower_values = values(Esql.azure_signinlogs_properties_app_display_name_lower), 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.@timestamp.min = min(@timestamp), Esql.@timestamp.max = max(@timestamp), Esql.event_count = count() by Esql.time_window_date_trunc | where Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct >= 15 and Esql.event_count >= 20 | keep Esql.time_window_date_trunc, Esql.event_count, Esql.@timestamp.min, Esql.@timestamp.max, Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct, Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values, Esql.azure_signinlogs_result_description_count_distinct, Esql.azure_signinlogs_result_description_values, Esql.azure_signinlogs_properties_status_error_code_count_distinct, Esql.azure_signinlogs_properties_status_error_code_values, Esql.azure_signinlogs_properties_incoming_token_type_lower_values, Esql.azure_signinlogs_properties_app_display_name_lower_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.azure_signinlogs_properties_authentication_requirement_values, Esql.azure_signinlogs_properties_app_id_values, Esql.azure_signinlogs_properties_app_display_name_values, Esql.azure_signinlogs_properties_resource_id_values, Esql.azure_signinlogs_properties_resource_display_name_values, Esql.azure_signinlogs_properties_conditional_access_status_values, Esql.azure_signinlogs_properties_device_detail_browser_values, Esql.azure_signinlogs_properties_device_detail_device_id_values, Esql.azure_signinlogs_properties_device_detail_operating_system_values, Esql.azure_signinlogs_properties_incoming_token_type_values, Esql.azure_signinlogs_properties_risk_state_values, Esql.azure_signinlogs_properties_session_id_values, Esql.azure_signinlogs_properties_user_id_values, Esql_priv.azure_signinlogs_properties_user_principal_name_values, Esql.azure_signinlogs_result_description_values, Esql.azure_signinlogs_result_signature_values, Esql.azure_signinlogs_result_type_values
Framework: MITRE ATT&CKTM
-
Tactic:
- Name: Credential Access
- ID: TA0006
- Reference URL: https://attack.mitre.org/tactics/TA0006/
-
Technique:
- Name: Brute Force
- ID: T1110
- Reference URL: https://attack.mitre.org/techniques/T1110/
-
Sub-technique:
- Name: Password Guessing
- ID: T1110.001
- Reference URL: https://attack.mitre.org/techniques/T1110/001/
-
Sub-technique:
- Name: Password Spraying
- ID: T1110.003
- Reference URL: https://attack.mitre.org/techniques/T1110/003/
-
Sub-technique:
- Name: Credential Stuffing
- ID: T1110.004
- Reference URL: https://attack.mitre.org/techniques/T1110/004/