diff --git a/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml b/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml index 6714355362f..e7bfb621a8a 100644 --- a/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml +++ b/rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml @@ -2,7 +2,8 @@ creation_date = "2025/07/01" integration = ["azure"] maturity = "production" -updated_date = "2025/09/26" +min_stack_version = "8.19.7" +updated_date = "2025/11/13" [rule] author = ["Elastic"] @@ -18,10 +19,11 @@ 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 @@ -30,14 +32,15 @@ This rule detects a high number of sign-in failures due to account lockouts (err ### 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 +58,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 +85,27 @@ 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 azure.signinlogs.properties.user_principal_name: "" + 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"