From 04b99c8ec1ea3f1aa3f22b7b5a28f3c5cb6d9af8 Mon Sep 17 00:00:00 2001 From: Terrance DeJesus <99630311+terrancedejesus@users.noreply.github.com> Date: Fri, 23 Jan 2026 16:25:51 -0500 Subject: [PATCH] [Rule Tuning] Entra ID OAuth Device Code Flow with Concurrent Sign-ins (#5594) Fixes #5593 --- ...s_azure_entra_susp_device_code_signin.toml | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/rules/integrations/azure/credential_access_azure_entra_susp_device_code_signin.toml b/rules/integrations/azure/credential_access_azure_entra_susp_device_code_signin.toml index b8ebfee25..a94c27b57 100644 --- a/rules/integrations/azure/credential_access_azure_entra_susp_device_code_signin.toml +++ b/rules/integrations/azure/credential_access_azure_entra_susp_device_code_signin.toml @@ -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,