Skip to content

Commit f6f3c48

Browse files
Merge branch 'main' into add_guides_nov_2
2 parents 775ad1b + 22a94c6 commit f6f3c48

File tree

2 files changed

+144
-107
lines changed

2 files changed

+144
-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"
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
[metadata]
2+
creation_date = "2025/10/22"
3+
integration = ["okta"]
4+
maturity = "production"
5+
updated_date = "2025/10/22"
6+
7+
[rule]
8+
author = ["Elastic"]
9+
description = """
10+
Identifies when a single Okta device token hash (dt_hash) is associated with multiple operating system types. This is
11+
highly anomalous because a device token is tied to a specific device and its operating system. This alert strongly
12+
indicates that an attacker has stolen a device token and is using it to impersonate a legitimate user from a
13+
different machine.
14+
"""
15+
false_positives = [
16+
"""
17+
Applications will tag the operating system as null when the device is not recognized as a managed device. In
18+
environments where users frequently switch between managed and unmanaged devices, this may lead to false positives.
19+
""",
20+
]
21+
from = "now-60m"
22+
index = ["logs-okta.system-*"]
23+
language = "kuery"
24+
license = "Elastic License v2"
25+
name = "Okta Multiple OS Names Detected for a Single DT Hash"
26+
note = """## Triage and analysis
27+
28+
### Investigating Okta Multiple OS Names Detected for a Single DT Hash
29+
30+
This rule detects when a single Okta device token hash (dt_hash) is associated with multiple operating system types. This is highly anomalous because a device token is tied to a specific device and its operating system. This alert strongly indicates that an attacker has stolen a device token token and is using it to impersonate a legitimate user from a different machine.
31+
32+
### Possible investigation steps
33+
- Review the `okta.debug_context.debug_data.dt_hash` field to identify the specific device
34+
trust hash associated with multiple operating systems.
35+
- Examine the `user.email` field to determine which user account is associated with the suspicious activity
36+
- Analyze the `source.ip` field to identify the IP addresses from which the different operating systems were reported. Look for any unusual or unexpected locations.
37+
- Review the `user_agent.os.name` field to see the different operating systems reported for the
38+
same dt_hash. This will help identify the nature of the anomaly.
39+
- Check the `event.action` field to understand the context of the authentication events (e.g., MFA verification, standard authentication).
40+
- Investigate the timeline of events to see when the different operating systems were reported for the same dt_hash. Look for patterns or sequences that may indicate malicious activity.
41+
- Correlate this activity with other security events in your environment, such as failed login attempts, unusual access patterns, or alerts from endpoint security solutions.
42+
43+
### False positive analysis
44+
- Applications will tag the operating system as null when the device is not recognized as a managed device
45+
- In environments where users frequently switch between managed and unmanaged devices, this may lead to false positives.
46+
47+
### Response and remediation
48+
- Immediately investigate the user account associated with the suspicious activity to determine if it has been compromised.
49+
- If compromise is confirmed, reset the user's credentials and enforce multi-factor authentication (MFA)
50+
- Revoke any active sessions associated with the compromised account to prevent further unauthorized access.
51+
- Review and monitor the affected dt_hash for any further suspicious activity.
52+
- Educate users about the importance of device security and the risks associated with device tokens.
53+
- Implement additional monitoring for device token tokens and consider using conditional access policies to restrict access based on device compliance status.
54+
"""
55+
risk_score = 73
56+
rule_id = "fb3ca230-af4e-11f0-900d-f661ea17fbcc"
57+
severity = "high"
58+
tags = [
59+
"Domain: Identity",
60+
"Data Source: Okta",
61+
"Data Source: Okta System Logs",
62+
"Use Case: Threat Detection",
63+
"Tactic: Credential Access",
64+
"Resources: Investigation Guide"
65+
]
66+
timestamp_override = "event.ingested"
67+
type = "threshold"
68+
69+
query = '''
70+
data_stream.dataset: "okta.system"
71+
and not okta.debug_context.debug_data.dt_hash: "-"
72+
and user_agent.os.name: *
73+
and event.action: (
74+
"user.authentication.verify" or
75+
"user.authentication.auth_via_mfa"
76+
)
77+
'''
78+
79+
80+
[[rule.threat]]
81+
framework = "MITRE ATT&CK"
82+
[[rule.threat.technique]]
83+
id = "T1539"
84+
name = "Steal Web Session Cookie"
85+
reference = "https://attack.mitre.org/techniques/T1539/"
86+
87+
88+
[rule.threat.tactic]
89+
id = "TA0006"
90+
name = "Credential Access"
91+
reference = "https://attack.mitre.org/tactics/TA0006/"
92+
93+
[rule.investigation_fields]
94+
field_names = [
95+
"@timestamp",
96+
"okta.debug_context.debug_data.dt_hash",
97+
"user.email",
98+
"source.ip",
99+
"user_agent.os.name",
100+
"event.action",
101+
]
102+
103+
[rule.threshold]
104+
field = ["okta.debug_context.debug_data.dt_hash"]
105+
value = 1
106+
[[rule.threshold.cardinality]]
107+
field = "user_agent.os.name"
108+
value = 2
109+
110+

0 commit comments

Comments
 (0)