Skip to content

Commit b76d92b

Browse files
terrancedejesustradebot-elastic
authored andcommitted
[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 (cherry picked from commit e8d7426)
1 parent 72272d0 commit b76d92b

File tree

1 file changed

+34
-107
lines changed

1 file changed

+34
-107
lines changed

rules/integrations/azure/credential_access_entra_id_excessive_account_lockouts.toml

Lines changed: 34 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
creation_date = "2025/07/01"
33
integration = ["azure"]
44
maturity = "production"
5-
updated_date = "2025/09/26"
5+
min_stack_version = "8.19.7"
6+
min_stack_comments = "Bug fix in threshold rules."
7+
updated_date = "2025/11/13"
68

79
[rule]
810
author = ["Elastic"]
@@ -18,26 +20,28 @@ false_positives = [
1820
""",
1921
]
2022
from = "now-60m"
21-
interval = "15m"
22-
language = "esql"
23+
index = ["filebeat-*", "logs-azure.signinlogs-*"]
24+
interval = "30m"
25+
language = "kuery"
2326
license = "Elastic License v2"
24-
name = "Microsoft Entra ID Exccessive Account Lockouts Detected"
27+
name = "Microsoft Entra ID Excessive Account Lockouts Detected"
2528
note = """## Triage and analysis
2629
27-
### Investigating Microsoft Entra ID Exccessive Account Lockouts Detected
30+
### Investigating Microsoft Entra ID Excessive Account Lockouts Detected
2831
2932
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.
3033
3134
### Possible investigation steps
3235
33-
- Review `user_id_list` and `user_principal_name`: Check if targeted users include high-value accounts such as administrators, service principals, or shared inboxes.
34-
- 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.
35-
- 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.
36-
- Inspect `device_detail_browser` and `user_agent`: Clients like `"Python Requests"` indicate scripted automation rather than legitimate login attempts.
37-
- Evaluate `unique_users` vs. `total_attempts`: A high ratio suggests distributed attacks across multiple accounts, characteristic of password spraying.
38-
- Correlate `client_app_display_name` and `incoming_token_type`: PowerShell or unattended sign-in clients may be targeted for automation or legacy auth bypass.
39-
- 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.
40-
- Validate time range (`first_seen`, `last_seen`): Determine whether the attack is a short burst or part of a longer campaign.
36+
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.
37+
38+
- Review users impacted by pivoting searching for `user.name` in events where `azure.signinlogs.properties.status.error_code` is `50053`.
39+
- Analyze source addresses associated with these lockouts. Identify whether the activity originated from known malicious infrastructure (e.g., VPNs, botnets, or public cloud providers).
40+
- 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.
41+
- A high ratio suggests distributed attacks across multiple accounts, characteristic of password spraying.
42+
- Correlate client apps associated such as PowerShell or unattended sign-in clients may be targeted for automation or legacy auth bypass.
43+
- 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.
44+
- 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.
4145
4246
### False positive analysis
4347
@@ -55,6 +59,7 @@ This rule detects a high number of sign-in failures due to account lockouts (err
5559
- Audit authentication methods in use, and enforce modern auth (OAuth, SAML) over legacy protocols.
5660
- Strengthen Conditional Access policies to reduce exposure from weak locations, apps, or clients.
5761
- Conduct credential hygiene audits to assess reuse and rotation for targeted accounts.
62+
- If false positives are identified, create exceptions for known benign sources, users or user agents to reduce noise.
5863
"""
5964
references = [
6065
"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 = [
8186
"Resources: Investigation Guide",
8287
]
8388
timestamp_override = "event.ingested"
84-
type = "esql"
89+
type = "threshold"
8590

8691
query = '''
87-
from logs-azure.signinlogs-*
88-
89-
| eval
90-
Esql.time_window_date_trunc = date_trunc(30 minutes, @timestamp),
91-
Esql_priv.azure_signinlogs_properties_user_principal_name_lower = to_lower(azure.signinlogs.properties.user_principal_name),
92-
Esql.azure_signinlogs_properties_incoming_token_type_lower = to_lower(azure.signinlogs.properties.incoming_token_type),
93-
Esql.azure_signinlogs_properties_app_display_name_lower = to_lower(azure.signinlogs.properties.app_display_name)
94-
95-
| where event.dataset == "azure.signinlogs"
96-
and event.category == "authentication"
97-
and azure.signinlogs.category in ("NonInteractiveUserSignInLogs", "SignInLogs")
98-
and event.outcome == "failure"
99-
and azure.signinlogs.properties.authentication_requirement == "singleFactorAuthentication"
100-
and azure.signinlogs.properties.status.error_code == 50053
101-
and azure.signinlogs.properties.user_principal_name is not null
102-
and azure.signinlogs.properties.user_principal_name != ""
103-
and source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK"
104-
105-
| stats
106-
Esql.azure_signinlogs_properties_authentication_requirement_values = values(azure.signinlogs.properties.authentication_requirement),
107-
Esql.azure_signinlogs_properties_app_id_values = values(azure.signinlogs.properties.app_id),
108-
Esql.azure_signinlogs_properties_app_display_name_values = values(azure.signinlogs.properties.app_display_name),
109-
Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id),
110-
Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name),
111-
Esql.azure_signinlogs_properties_conditional_access_status_values = values(azure.signinlogs.properties.conditional_access_status),
112-
Esql.azure_signinlogs_properties_device_detail_browser_values = values(azure.signinlogs.properties.device_detail.browser),
113-
Esql.azure_signinlogs_properties_device_detail_device_id_values = values(azure.signinlogs.properties.device_detail.device_id),
114-
Esql.azure_signinlogs_properties_device_detail_operating_system_values = values(azure.signinlogs.properties.device_detail.operating_system),
115-
Esql.azure_signinlogs_properties_incoming_token_type_values = values(azure.signinlogs.properties.incoming_token_type),
116-
Esql.azure_signinlogs_properties_risk_state_values = values(azure.signinlogs.properties.risk_state),
117-
Esql.azure_signinlogs_properties_session_id_values = values(azure.signinlogs.properties.session_id),
118-
Esql.azure_signinlogs_properties_user_id_values = values(azure.signinlogs.properties.user_id),
119-
Esql_priv.azure_signinlogs_properties_user_principal_name_values = values(azure.signinlogs.properties.user_principal_name),
120-
Esql.azure_signinlogs_result_description_values = values(azure.signinlogs.result_description),
121-
Esql.azure_signinlogs_result_signature_values = values(azure.signinlogs.result_signature),
122-
Esql.azure_signinlogs_result_type_values = values(azure.signinlogs.result_type),
123-
124-
Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct = count_distinct(Esql_priv.azure_signinlogs_properties_user_principal_name_lower),
125-
Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values = values(Esql_priv.azure_signinlogs_properties_user_principal_name_lower),
126-
Esql.azure_signinlogs_result_description_count_distinct = count_distinct(azure.signinlogs.result_description),
127-
Esql.azure_signinlogs_properties_status_error_code_count_distinct = count_distinct(azure.signinlogs.properties.status.error_code),
128-
Esql.azure_signinlogs_properties_status_error_code_values = values(azure.signinlogs.properties.status.error_code),
129-
Esql.azure_signinlogs_properties_incoming_token_type_lower_values = values(Esql.azure_signinlogs_properties_incoming_token_type_lower),
130-
Esql.azure_signinlogs_properties_app_display_name_lower_values = values(Esql.azure_signinlogs_properties_app_display_name_lower),
131-
Esql.source_ip_values = values(source.ip),
132-
Esql.source_ip_count_distinct = count_distinct(source.ip),
133-
Esql.source_as_organization_name_values = values(source.`as`.organization.name),
134-
Esql.source_as_organization_name_count_distinct = count_distinct(source.`as`.organization.name),
135-
Esql.source_geo_country_name_values = values(source.geo.country_name),
136-
Esql.source_geo_country_name_count_distinct = count_distinct(source.geo.country_name),
137-
[email protected] = min(@timestamp),
138-
[email protected] = max(@timestamp),
139-
Esql.event_count = count()
140-
by Esql.time_window_date_trunc
141-
142-
| where Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct >= 15 and Esql.event_count >= 20
143-
144-
| keep
145-
Esql.time_window_date_trunc,
146-
Esql.event_count,
147-
148-
149-
Esql.azure_signinlogs_properties_user_principal_name_lower_count_distinct,
150-
Esql_priv.azure_signinlogs_properties_user_principal_name_lower_values,
151-
Esql.azure_signinlogs_result_description_count_distinct,
152-
Esql.azure_signinlogs_result_description_values,
153-
Esql.azure_signinlogs_properties_status_error_code_count_distinct,
154-
Esql.azure_signinlogs_properties_status_error_code_values,
155-
Esql.azure_signinlogs_properties_incoming_token_type_lower_values,
156-
Esql.azure_signinlogs_properties_app_display_name_lower_values,
157-
Esql.source_ip_values,
158-
Esql.source_ip_count_distinct,
159-
Esql.source_as_organization_name_values,
160-
Esql.source_as_organization_name_count_distinct,
161-
Esql.source_geo_country_name_values,
162-
Esql.source_geo_country_name_count_distinct,
163-
Esql.azure_signinlogs_properties_authentication_requirement_values,
164-
Esql.azure_signinlogs_properties_app_id_values,
165-
Esql.azure_signinlogs_properties_app_display_name_values,
166-
Esql.azure_signinlogs_properties_resource_id_values,
167-
Esql.azure_signinlogs_properties_resource_display_name_values,
168-
Esql.azure_signinlogs_properties_conditional_access_status_values,
169-
Esql.azure_signinlogs_properties_device_detail_browser_values,
170-
Esql.azure_signinlogs_properties_device_detail_device_id_values,
171-
Esql.azure_signinlogs_properties_device_detail_operating_system_values,
172-
Esql.azure_signinlogs_properties_incoming_token_type_values,
173-
Esql.azure_signinlogs_properties_risk_state_values,
174-
Esql.azure_signinlogs_properties_session_id_values,
175-
Esql.azure_signinlogs_properties_user_id_values,
176-
Esql_priv.azure_signinlogs_properties_user_principal_name_values,
177-
Esql.azure_signinlogs_result_description_values,
178-
Esql.azure_signinlogs_result_signature_values,
179-
Esql.azure_signinlogs_result_type_values
92+
event.dataset: "azure.signinlogs" and event.category: "authentication"
93+
and azure.signinlogs.category: ("NonInteractiveUserSignInLogs" or "SignInLogs")
94+
and event.outcome: "failure"
95+
and azure.signinlogs.properties.authentication_requirement: "singleFactorAuthentication"
96+
and azure.signinlogs.properties.status.error_code: 50053
97+
and azure.signinlogs.properties.user_principal_name: (* and not "")
98+
and not source.as.organization.name: "MICROSOFT-CORP-MSN-as-BLOCK"
18099
'''
181100

101+
[rule.threshold]
102+
field = []
103+
value = 20
104+
105+
[[rule.threshold.cardinality]]
106+
field = "user.name"
107+
value = 15
108+
182109

183110
[[rule.threat]]
184111
framework = "MITRE ATT&CK"

0 commit comments

Comments
 (0)