[Rule Tuning] Microsoft Entra ID Exccessive Account Lockouts (#5315)
* [Rule Tuning] Microsoft Entra ID Exccessive Account Lockouts Fixes #5314 * added min stack * added index * fixed query optimization * fixed investigation guide * added min-stack comments
This commit is contained in:
+34
-107
@@ -2,7 +2,9 @@
|
||||
creation_date = "2025/07/01"
|
||||
integration = ["azure"]
|
||||
maturity = "production"
|
||||
updated_date = "2025/09/26"
|
||||
min_stack_version = "8.19.7"
|
||||
min_stack_comments = "Bug fix in threshold rules."
|
||||
updated_date = "2025/11/13"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
@@ -18,26 +20,28 @@ false_positives = [
|
||||
""",
|
||||
]
|
||||
from = "now-60m"
|
||||
interval = "15m"
|
||||
language = "esql"
|
||||
index = ["filebeat-*", "logs-azure.signinlogs-*"]
|
||||
interval = "30m"
|
||||
language = "kuery"
|
||||
license = "Elastic License v2"
|
||||
name = "Microsoft Entra ID Exccessive Account Lockouts Detected"
|
||||
name = "Microsoft Entra ID Excessive Account Lockouts Detected"
|
||||
note = """## Triage and analysis
|
||||
|
||||
### Investigating Microsoft Entra ID Exccessive Account Lockouts Detected
|
||||
### Investigating Microsoft 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.
|
||||
|
||||
### 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.
|
||||
Please note this is as threshold rule that aggregates multiple account lockouts over a specified time window. To properly investigate, pivot into the individual sign-in log events that contributed to the threshold being met.
|
||||
|
||||
- Review users impacted by pivoting searching for `user.name` in events where `azure.signinlogs.properties.status.error_code` is `50053`.
|
||||
- Analyze source addresses associated with these lockouts. Identify whether the activity originated from known malicious infrastructure (e.g., VPNs, botnets, or public cloud providers).
|
||||
- Inspect the user-agents involved in these account lockouts. Clients like `Python Requests` indicate scripted automation rather than legitimate login attempts. ROPC agents may suggest brute-forcing against legacy auth.
|
||||
- A high ratio suggests distributed attacks across multiple accounts, characteristic of password spraying.
|
||||
- Correlate client apps associated such as PowerShell or unattended sign-in clients may be targeted for automation or legacy auth bypass.
|
||||
- Review conditional access state or risk state of the user involved. If Conditional Access was not applied and risk was not flagged, policy scope or coverage should be reviewed.
|
||||
- Check for any successful sign-ins for the affected users around the same time frame to determine if any accounts were compromised prior to lockout.
|
||||
|
||||
### False positive analysis
|
||||
|
||||
@@ -55,6 +59,7 @@ This rule detects a high number of sign-in failures due to account lockouts (err
|
||||
- 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.
|
||||
- If false positives are identified, create exceptions for known benign sources, users or user agents to reduce noise.
|
||||
"""
|
||||
references = [
|
||||
"https://www.microsoft.com/en-us/security/blog/2025/05/27/new-russia-affiliated-actor-void-blizzard-targets-critical-sectors-for-espionage/",
|
||||
@@ -81,104 +86,26 @@ tags = [
|
||||
"Resources: Investigation Guide",
|
||||
]
|
||||
timestamp_override = "event.ingested"
|
||||
type = "esql"
|
||||
type = "threshold"
|
||||
|
||||
query = '''
|
||||
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
|
||||
event.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"
|
||||
'''
|
||||
|
||||
[rule.threshold]
|
||||
field = []
|
||||
value = 20
|
||||
|
||||
[[rule.threshold.cardinality]]
|
||||
field = "user.name"
|
||||
value = 15
|
||||
|
||||
|
||||
[[rule.threat]]
|
||||
framework = "MITRE ATT&CK"
|
||||
|
||||
Reference in New Issue
Block a user