[Rule Tuning] Entra ID OAuth Device Code Flow with Concurrent Sign-ins (#5594)
Fixes #5593
This commit is contained in:
+20
-15
@@ -2,19 +2,22 @@
|
||||
creation_date = "2025/12/02"
|
||||
integration = ["azure"]
|
||||
maturity = "production"
|
||||
updated_date = "2025/12/10"
|
||||
updated_date = "2026/01/21"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
description = """
|
||||
Identifies concurrent Entra ID sign-in events for the same user and session from multiple sources, and where one of the
|
||||
authentication event has some suspicious properties often associated to DeviceCode and OAuth phishing. Adversaries may
|
||||
steal Refresh Tokens (RTs) via phishing to bypass multi-factor authentication (MFA) and gain unauthorized access to
|
||||
Azure resources.
|
||||
Identifies Entra ID device code authentication flows where multiple user agents are observed within the same session.
|
||||
This pattern is indicative of device code phishing, where an attacker's polling client (e.g., Python script) and the
|
||||
victim's browser both appear in the same authentication session. In legitimate device code flows, the user authenticates
|
||||
via browser while the requesting application polls for tokens - when these have distinctly different user agents
|
||||
(e.g., Python Requests vs Chrome), it may indicate the code was phished and redeemed by an attacker.
|
||||
"""
|
||||
false_positives = [
|
||||
"""
|
||||
Users authenticating from multiple devices and using the deviceCode protocol or the Visual Studio Code client.
|
||||
Legitimate use of device code flow where a user authenticates via browser for a CLI tool or headless application.
|
||||
Common legitimate scenarios include Azure CLI, Azure PowerShell, or VS Code remote development. Review the user
|
||||
agent combinations - browser + known CLI tool from the same user may be expected behavior.
|
||||
""",
|
||||
]
|
||||
from = "now-9m"
|
||||
@@ -75,27 +78,29 @@ from logs-azure.signinlogs-* metadata _id, _version, _index
|
||||
| where event.category == "authentication" and event.dataset == "azure.signinlogs" and
|
||||
azure.signinlogs.properties.original_transfer_method == "deviceCodeFlow"
|
||||
|
||||
| Eval Esql.interactive_logon = CASE(azure.signinlogs.category == "SignInLogs", source.ip, null),
|
||||
Esql.non_interactive_logon = CASE(azure.signinlogs.category == "NonInteractiveUserSignInLogs", source.ip, null)
|
||||
// Track events with deviceCode authentication protocol (browser auth) vs polling client
|
||||
| eval is_device_code_auth = case(azure.signinlogs.properties.authentication_protocol == "deviceCode", 1, 0)
|
||||
|
||||
| stats Esql.count_logon = count(*),
|
||||
Esql.device_code_auth_count = sum(is_device_code_auth),
|
||||
Esql.timestamp_values = values(@timestamp),
|
||||
Esql.source_ip_count_distinct = count_distinct(source.ip),
|
||||
Esql.is_interactive = count(Esql.interactive_logon),
|
||||
Esql.is_non_interactive = count(Esql.non_interactive_logon),
|
||||
Esql.user_agent_count_distinct = COUNT_DISTINCT(user_agent.original),
|
||||
Esql.user_agent_values = VALUES(user_agent.original),
|
||||
Esql.user_agent_count_distinct = count_distinct(user_agent.original),
|
||||
Esql.user_agent_values = values(user_agent.original),
|
||||
Esql.authentication_protocol_values = values(azure.signinlogs.properties.authentication_protocol),
|
||||
Esql.azure_signinlogs_properties_client_app_values = values(azure.signinlogs.properties.app_display_name),
|
||||
Esql.azure_signinlogs_properties_client_app_values = values(azure.signinlogs.properties.app_id),
|
||||
Esql.azure_signinlogs_properties_app_id_values = values(azure.signinlogs.properties.app_id),
|
||||
Esql.azure_signinlogs_properties_resource_display_name_values = values(azure.signinlogs.properties.resource_display_name),
|
||||
Esql.azure_signinlogs_properties_auth_requirement_values = values(azure.signinlogs.properties.authentication_requirement),
|
||||
Esql.azure_signinlogs_properties_tenant_id = values(azure.tenant_id),
|
||||
Esql.azure_signinlogs_properties_status_error_code_values = values(azure.signinlogs.properties.status.error_code),
|
||||
Esql.message_values = values(message),
|
||||
Esql.azure_signinlogs_properties_resource_id_values = values(azure.signinlogs.properties.resource_id),
|
||||
Esql.source_ip_values = VALUES(source.ip) by azure.signinlogs.properties.session_id, azure.signinlogs.identity
|
||||
Esql.source_ip_values = values(source.ip)
|
||||
by azure.signinlogs.properties.session_id, azure.signinlogs.identity
|
||||
|
||||
| where Esql.is_interactive >= 2 and Esql.is_non_interactive >= 1 and (Esql.source_ip_count_distinct >= 2 or Esql.user_agent_count_distinct >= 2)
|
||||
// Require: 2+ events, at least one deviceCode auth protocol event, and either 2+ IPs or 2+ user agents
|
||||
| where Esql.count_logon >= 2 and Esql.device_code_auth_count >= 1 and (Esql.source_ip_count_distinct >= 2 or Esql.user_agent_count_distinct >= 2)
|
||||
| keep
|
||||
Esql.*,
|
||||
azure.signinlogs.properties.session_id,
|
||||
|
||||
Reference in New Issue
Block a user