Microsoft Entra ID Exccessive Account Lockouts Detected

edit
A newer version is available. Check out the latest documentation.

Microsoft Entra ID Exccessive Account Lockouts Detected

edit

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: 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:

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

edit

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.

Rule query

edit
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&CKTM