Discovery (TA0007)(external, opens in a new tab or window)
Defense Evasion (TA0005)(external, opens in a new tab or window)
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).