EXPLORE
← Back to Explore
elastichighTTP

Entra ID Excessive 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.

MITRE ATT&CK

credential-access

Detection Query

data_stream.dataset: "azure.signinlogs" and event.category: "authentication"
    and azure.signinlogs.category: ("NonInteractiveUserSignInLogs" or "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: (* and not "")
    and not source.as.organization.name: "MICROSOFT-CORP-MSN-as-BLOCK"
    and not source.ip: ("10.0.0.0/8" or "172.16.0.0/12" or "192.168.0.0/16" or "127.0.0.0/8" or "::1/128" or "fd00::/8")

Author

Elastic

Created

2025/07/01

Data Sources

AzureEntra IDEntra ID Sign-in Logsfilebeat-*logs-azure.signinlogs-*

Tags

Domain: CloudDomain: IdentityData Source: AzureData Source: Entra IDData Source: Entra ID Sign-in LogsUse Case: Identity and Access AuditUse Case: Threat DetectionTactic: Credential AccessResources: Investigation Guide
Raw Content
[metadata]
creation_date = "2025/07/01"
integration = ["azure"]
maturity = "production"
min_stack_comments = "Changing min stack to 9.1.0, the latest minimum supported version for 9.X releases."
min_stack_version = "9.1.0"
updated_date = "2026/04/10"

[rule]
author = ["Elastic"]
description = """
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.
"""
false_positives = [
    """
    Automated processes that attempt to authenticate using expired credentials or have misconfigured authentication
    settings may lead to false positives.
    """,
]
from = "now-30m"
index = ["filebeat-*", "logs-azure.signinlogs-*"]
interval = "15m"
language = "kuery"
license = "Elastic License v2"
name = "Entra ID Excessive Account Lockouts Detected"
note = """## Triage and analysis

### Investigating Entra ID Excessive 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. Error code `50053` indicates Entra ID Smart Lockout, which is IP-based and tracks "unfamiliar" vs "familiar" locations. Familiar IPs have a higher threshold before lockout, so if lockouts are occurring, the source IP was unfamiliar to those accounts.

### Possible investigation steps

- This is a threshold rule that aggregates events, so the alert document won't contain individual sign-in attempts. Pivot to the raw logs in Discover or Timeline using: `event.dataset: "azure.signinlogs" and azure.signinlogs.properties.status.error_code: 50053`. Match the time range to the alert window (rule lookback is 30 minutes).
- Add relevant columns: `@timestamp`, `user.name`, `source.ip`, `user_agent.original`, `azure.signinlogs.properties.app_display_name`, `azure.signinlogs.properties.client_app_used`, `source.as.organization.name`, `geo.country_iso_code`, `azure.signinlogs.properties.authentication_details`.
- Aggregate by `source.ip` and `user.name` to identify the attack pattern: password spray (single/small set of IPs targeting many accounts), credential stuffing (many source IPs, may target specific users with leaked credentials), targeted brute force (focused on single high-value account), or misconfigured service (single source IP with consistent user-agent matching legitimate application).
- Analyze `user_agent.original` for indicators of automation. Values like `curl/*`, `python-requests/*`, `Go-http-client/*`, `axios/*` indicate scripted attacks. `BAV2ROPC` indicates Resource Owner Password Credential flow (legacy auth), commonly abused by spray tools like o365spray and MSOLSpray.
- Review `azure.signinlogs.properties.client_app_used`. `Other clients` indicates legacy auth (IMAP, SMTP, POP) which is high-risk. `Exchange ActiveSync` is legacy auth often targeted.
- Check `azure.signinlogs.properties.authentication_protocol` for `ropc` (Resource Owner Password Credential), a legacy flow where credentials are sent directly with no MFA interruption possible.
- Review `source.as.organization.name`. Cloud hosting providers (DigitalOcean, Vultr, Linode, AWS, Azure, GCP) or VPN providers (NordVPN, ExpressVPN, Mullvad) are high-risk as attackers use this infrastructure.
- Check `azure.signinlogs.properties.conditional_access_status`. `notApplied` means no CA policy evaluated, indicating a coverage gap. `failure` means CA policy blocked the sign-in.
- Determine if any accounts were compromised. Query for successful sign-ins from attacking infrastructure: `event.dataset: "azure.signinlogs" and event.outcome: "success" and source.ip: "<attacker_ip>"`. Also query for successful sign-ins for affected users from any source during the attack window.
- Check for MFA bypass: `event.dataset: "azure.signinlogs" and event.outcome: "success" and azure.signinlogs.properties.authentication_requirement: "singleFactorAuthentication" and source.ip: "<attacker_ip>"`
- Review Entra ID Protection risk events for affected users by checking `risk_level_during_signin`, `risk_level_aggregated`, and `risk_state` for values other than `none`.
- Check for username enumeration preceding the spray. Error code `50034` (user not found) indicates enumeration: `event.dataset: "azure.signinlogs" and azure.signinlogs.properties.status.error_code: 50034 and source.ip: "<attacker_ip>"`
- Query for other error codes from the same source: `50126` (invalid password), `50034` (user not found), `50057` (account disabled), `50055` (password expired), `53003` (blocked by CA), `50158` (MFA challenge not satisfied). A mix of `50126` and `50053` confirms password guessing.

### False positive analysis

- Misconfigured clients, scripts, or services with outdated credentials may inadvertently cause lockouts.
- Lockouts during credential rotation windows could be benign.
- Legacy applications without modern auth support may repeatedly fail and trigger Smart Lockout.
- Specific known user agents from corporate service accounts.
- Cloud-hosted automation with expected failure behavior.
- This rule can be customized. Adjust the threshold and/or interval/lookback window based on baselining efforts. Specific UPNs can be excluded if they generate known false positives.

### Response and remediation

- Confirm no successful authentications from attacking source.
- Query for successful sign-ins to affected users from any source during the attack window.
- If compromise confirmed: disable account, revoke refresh tokens (`Revoke-AzureADUserAllRefreshToken` or via portal), reset password, require MFA re-registration.
- Block source IP/ASN via Conditional Access named locations.
- If legacy auth was used: create CA policy blocking legacy authentication.
- If MFA wasn't required: review CA policy coverage for gaps.
- Document affected users for credential reset and monitoring.
"""
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",
]
risk_score = 73
rule_id = "2d6f5332-42ea-11f0-b09a-f661ea17fbcd"
severity = "high"
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",
]
timestamp_override = "event.ingested"
type = "threshold"

query = '''
data_stream.dataset: "azure.signinlogs" and event.category: "authentication"
    and azure.signinlogs.category: ("NonInteractiveUserSignInLogs" or "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: (* and not "")
    and not source.as.organization.name: "MICROSOFT-CORP-MSN-as-BLOCK"
    and not source.ip: ("10.0.0.0/8" or "172.16.0.0/12" or "192.168.0.0/16" or "127.0.0.0/8" or "::1/128" or "fd00::/8")
'''


[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1110"
name = "Brute Force"
reference = "https://attack.mitre.org/techniques/T1110/"
[[rule.threat.technique.subtechnique]]
id = "T1110.001"
name = "Password Guessing"
reference = "https://attack.mitre.org/techniques/T1110/001/"

[[rule.threat.technique.subtechnique]]
id = "T1110.003"
name = "Password Spraying"
reference = "https://attack.mitre.org/techniques/T1110/003/"

[[rule.threat.technique.subtechnique]]
id = "T1110.004"
name = "Credential Stuffing"
reference = "https://attack.mitre.org/techniques/T1110/004/"



[rule.threat.tactic]
id = "TA0006"
name = "Credential Access"
reference = "https://attack.mitre.org/tactics/TA0006/"

[rule.threshold]
field = []
value = 30
[[rule.threshold.cardinality]]
field = "user.name"
value = 25