-
Notifications
You must be signed in to change notification settings - Fork 609
[Rule Tuning] Microsoft Entra ID Exccessive Account Lockouts #5315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
terrancedejesus
wants to merge
3
commits into
main
Choose a base branch
from
terrancedejesus/issue5314
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+33
−106
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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), | ||
| [email protected] = min(@timestamp), | ||
| [email protected] = 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, | ||
| [email protected], | ||
| [email protected], | ||
| 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" | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@terrancedejesus have u tried to simulate this rule and see the if from the corresponding alert analyst can exclude specific user.name as FP (similar to what the original SDH objective) ?