Entra ID Managed Device Client Sign-In to Graph Enumeration

Last updated 2 months ago on 2026-09-17
Created 2 months ago on 2026-09-17

About

Identifies a successful non-interactive Microsoft Entra ID client sign-in for Microsoft Graph that carries managed, joined device context and reports an Edge client when the exact access token is subsequently used by a scripted client to query at least three distinct Microsoft Graph directory-enumeration paths. The rule correlates the Entra sign-in token identifier with Microsoft Graph activity rather than relying on the broader session identifier, which can be shared by legitimate application activity and multiple tokens.
Tags
Domain: CloudDomain: IdentityData Source: AzureData Source: Microsoft Graph Activity LogsPlatform: Entra IDUse Case: Identity and Access AuditUse Case: Threat DetectionRule Type: ESQLTactic: Defense EvasionTactic: DiscoveryLanguage: esql
Severity
medium
Risk Score
47
MITRE ATT&CK™

Discovery (TA0007)(external, opens in a new tab or window)

Defense Evasion (TA0005)(external, opens in a new tab or window)

False Positive Examples
Administrators and developers may legitimately acquire tokens and use them with PowerShell, command-line tools, or custom scripts to query Microsoft Graph. Confirm that the client application, account, enumeration target, and timing are expected before escalating.
License
Elastic License v2(external, opens in a new tab or window)

Definition

Integration Pack
Prebuilt Security Detection Rules
Related Integrations

azure(external, opens in a new tab or window)

Query
text code block:
FROM logs-azure.signinlogs-*, logs-azure.graphactivitylogs-* METADATA _id, _version, _index // normalize the token, user, app, device and session ids | EVAL Esql.token_id = COALESCE( azure.signinlogs.properties.unique_token_identifier, token.id ), Esql.session_id = COALESCE( azure.signinlogs.properties.session_id, azure.graphactivitylogs.properties.c_sid ), Esql.user_id = COALESCE( azure.signinlogs.properties.user_id, azure.graphactivitylogs.properties.user_principal_object_id ), Esql.app_id = COALESCE( azure.signinlogs.properties.app_id, azure.graphactivitylogs.properties.app_id ), Esql.device_id = COALESCE( azure.signinlogs.properties.device_detail.device_id, azure.graphactivitylogs.properties.c_device_id ), Esql.user_agent_original_lower = TO_LOWER(user_agent.original), Esql.url_path_lower = TO_LOWER(url.path) // filter for Entra ID client sign-in and token acquisition context | WHERE Esql.token_id IS NOT NULL AND Esql.session_id IS NOT NULL AND Esql.user_id IS NOT NULL AND Esql.app_id IS NOT NULL AND Esql.device_id IS NOT NULL AND Esql.device_id != "" | EVAL Esql.azure_signinlogs_is_client_signin = event.dataset == "azure.signinlogs" AND azure.signinlogs.properties.status.error_code == 0 AND azure.signinlogs.properties.device_detail.trust_type == "Azure AD joined" AND azure.signinlogs.properties.device_detail.device_id IS NOT NULL AND azure.signinlogs.properties.device_detail.device_id != "" AND azure.signinlogs.properties.device_detail.is_managed == true AND azure.signinlogs.properties.is_interactive == false AND azure.signinlogs.properties.incoming_token_type IN ("refreshToken", "primaryRefreshToken") AND azure.signinlogs.properties.client_app_used == "Mobile Apps and Desktop clients" AND azure.signinlogs.properties.resource_id == "00000003-0000-0000-c000-000000000000" AND user_agent.name == "Edge" // filter for scripted Graph enumeration for a user identity // based on red teaming tooling and in-the-wild observations | EVAL Esql.azure_graphactivitylogs_is_scripted_enum = event.dataset == "azure.graphactivitylogs" AND azure.graphactivitylogs.properties.c_idtyp == "user" AND azure.graphactivitylogs.properties.client_auth_method == 0 AND http.request.method == "GET" AND ( Esql.user_agent_original_lower LIKE "*powershell*" OR Esql.user_agent_original_lower LIKE "*python-requests/*" OR Esql.user_agent_original_lower LIKE "*python-httpx/*" OR Esql.user_agent_original_lower LIKE "*curl/*" OR Esql.user_agent_original_lower LIKE "*aiohttp/*" OR Esql.user_agent_original_lower LIKE "*fasthttp*" OR Esql.user_agent_original_lower LIKE "*go-http-client/*" OR Esql.user_agent_original_lower LIKE "*go-resty/*" OR Esql.user_agent_original_lower LIKE "*okhttp/*" OR Esql.user_agent_original_lower LIKE "*restsharp/*" OR Esql.user_agent_original_lower LIKE "*postmanruntime/*" OR Esql.user_agent_original_lower LIKE "*axios/*" OR Esql.user_agent_original_lower == "node" OR Esql.user_agent_original_lower LIKE "*node-fetch/*" OR Esql.user_agent_original_lower LIKE "*undici*" OR Esql.user_agent_original_lower LIKE "*azurehound/*" OR Esql.user_agent_original_lower LIKE "*sharphound/*" OR Esql.user_agent_original_lower LIKE "*bloodhound/*" OR Esql.user_agent_original_lower LIKE "*aadinternals*" OR Esql.user_agent_original_lower LIKE "*bav2ropc*" OR Esql.user_agent_original_lower LIKE "*roadtools*" OR Esql.user_agent_original_lower LIKE "*roadrecon*" OR Esql.user_agent_original_lower LIKE "*roadtx*" OR Esql.user_agent_original_lower LIKE "*graphpython*" OR Esql.user_agent_original_lower LIKE "*msgraphx*" ) AND ( // Documented collection reads. Query parameters are not part of url.path. Esql.url_path_lower IN ( "/v1.0/users", "/beta/users", "/v1.0/groups", "/beta/groups", "/v1.0/applications", "/beta/applications", "/v1.0/serviceprincipals", "/beta/serviceprincipals", "/v1.0/devices", "/beta/devices", "/v1.0/directoryroles", "/beta/directoryroles", "/v1.0/organization", "/beta/organization", "/v1.0/subscribedskus", "/beta/subscribedskus", "/v1.0/rolemanagement/directory/roledefinitions", "/beta/rolemanagement/directory/roledefinitions", "/v1.0/rolemanagement/directory/roleassignments", "/beta/rolemanagement/directory/roleassignments", "/v1.0/rolemanagement/directory/roleeligibilityschedules", "/beta/rolemanagement/directory/roleeligibilityschedules", "/v1.0/rolemanagement/directory/roleeligibilityscheduleinstances", "/beta/rolemanagement/directory/roleeligibilityscheduleinstances" ) ) | WHERE Esql.azure_signinlogs_is_client_signin OR Esql.azure_graphactivitylogs_is_scripted_enum // aggregate the events for the rule by session, user, app, device, token and tenant | STATS Esql.azure_signinlogs_properties_id_count_distinct = COUNT_DISTINCT( azure.signinlogs.properties.id ) WHERE Esql.azure_signinlogs_is_client_signin, Esql.azure_graphactivitylogs_properties_operation_id_count_distinct = COUNT_DISTINCT( azure.graphactivitylogs.properties.operation_id ) WHERE Esql.azure_graphactivitylogs_is_scripted_enum, Esql.url_path_lower_count_distinct = COUNT_DISTINCT( Esql.url_path_lower ) WHERE Esql.azure_graphactivitylogs_is_scripted_enum, Esql.timestamp_client_signin_min = MIN( CASE(Esql.azure_signinlogs_is_client_signin, @timestamp, NULL) ), Esql.timestamp_scripted_enum_min = MIN( CASE(Esql.azure_graphactivitylogs_is_scripted_enum, @timestamp, NULL) ), Esql.user_agent_original_client_signin_values = VALUES( user_agent.original ) WHERE Esql.azure_signinlogs_is_client_signin, Esql.user_agent_original_scripted_enum_values = VALUES( user_agent.original ) WHERE Esql.azure_graphactivitylogs_is_scripted_enum, Esql.url_path_values = VALUES(url.path) WHERE Esql.azure_graphactivitylogs_is_scripted_enum, Esql.url_original_values = VALUES(url.original) WHERE Esql.azure_graphactivitylogs_is_scripted_enum, Esql.source_ip_values = VALUES(source.ip), Esql.azure_signinlogs_properties_id_values = VALUES( azure.signinlogs.properties.id ) WHERE Esql.azure_signinlogs_is_client_signin, Esql.azure_graphactivitylogs_properties_operation_id_values = VALUES( azure.graphactivitylogs.properties.operation_id ) WHERE Esql.azure_graphactivitylogs_is_scripted_enum, Esql.azure_signinlogs_properties_app_display_name_values = VALUES( azure.signinlogs.properties.app_display_name ) WHERE Esql.azure_signinlogs_is_client_signin, Esql_priv.azure_signinlogs_properties_user_principal_name_values = VALUES( azure.signinlogs.properties.user_principal_name ) WHERE Esql.azure_signinlogs_is_client_signin BY Esql.token_id, Esql.session_id, Esql.user_id, Esql.app_id, Esql.device_id, azure.tenant_id // filter by client sign-in and scripted Graph enumeration events | WHERE Esql.azure_signinlogs_properties_id_count_distinct > 0 AND Esql.azure_graphactivitylogs_properties_operation_id_count_distinct > 0 AND Esql.url_path_lower_count_distinct >= 3 AND Esql.timestamp_client_signin_min IS NOT NULL AND Esql.timestamp_scripted_enum_min IS NOT NULL AND Esql.timestamp_scripted_enum_min >= Esql.timestamp_client_signin_min | KEEP Esql.*, Esql_priv.*, azure.tenant_id

Install detection rules in Elastic Security

Detect Entra ID Managed Device Client Sign-In to Graph Enumeration in the Elastic Security detection engine by installing this rule into your Elastic Stack.

To setup this rule, check out the installation guide for Prebuilt Security Detection Rules(external, opens in a new tab or window).