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.
Rule type: esql
Rule indices:
Rule Severity: high
Risk Score: 73
Runs every: 15m
Searches indices from: now-60m
Maximum alerts per execution: ?
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: ?
Rule authors:
- Elastic
Rule license: Elastic License v2
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.
- 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.
- 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.
- 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.
from 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&CK
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/