from logs-azure.signinlogs-*, logs-azure.graphactivitylogs-* metadata _id, _version, _index
| where
(event.dataset == "azure.signinlogs"
and source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK"
and azure.signinlogs.properties.session_id is not null)
or
(event.dataset == "azure.graphactivitylogs"
and source.`as`.organization.name != "MICROSOFT-CORP-MSN-as-BLOCK"
and azure.graphactivitylogs.properties.c_sid is not null)
| eval
Esql.azure_signinlogs_properties_session_id_coalesce = coalesce(azure.signinlogs.properties.session_id, azure.graphactivitylogs.properties.c_sid),
Esql.azure_signinlogs_properties_user_id_coalesce = coalesce(azure.signinlogs.properties.user_id, azure.graphactivitylogs.properties.user_principal_object_id),
Esql.azure_signinlogs_properties_app_id_coalesce = coalesce(azure.signinlogs.properties.app_id, azure.graphactivitylogs.properties.app_id),
Esql.source_ip = source.ip,
Esql.@timestamp = @timestamp,
Esql.event_type_case = case(
event.dataset == "azure.signinlogs", "signin",
event.dataset == "azure.graphactivitylogs", "graph",
"other"
),
Esql.time_window_date_trunc = date_trunc(5 minutes, @timestamp)
| keep
Esql.azure_signinlogs_properties_session_id_coalesce,
Esql.source_ip,
Esql.@timestamp,
Esql.event_type_case,
Esql.time_window_date_trunc,
Esql.azure_signinlogs_properties_user_id_coalesce,
Esql.azure_signinlogs_properties_app_id_coalesce
| stats
Esql.azure_signinlogs_properties_user_id_coalesce_values = values(Esql.azure_signinlogs_properties_user_id_coalesce),
Esql.azure_signinlogs_properties_session_id_coalesce_values = values(Esql.azure_signinlogs_properties_session_id_coalesce),
Esql.source_ip_values = values(Esql.source_ip),
Esql.source_ip_count_distinct = count_distinct(Esql.source_ip),
Esql.azure_signinlogs_properties_app_id_coalesce_values = values(Esql.azure_signinlogs_properties_app_id_coalesce),
Esql.azure_signinlogs_properties_app_id_coalesce_count_distinct = count_distinct(Esql.azure_signinlogs_properties_app_id_coalesce),
Esql.event_type_case_values = values(Esql.event_type_case),
Esql.event_type_case_count_distinct = count_distinct(Esql.event_type_case),
Esql.@timestamp.min = min(Esql.@timestamp),
Esql.@timestamp.max = max(Esql.@timestamp),
Esql.signin_time_min = min(case(Esql.event_type_case == "signin", Esql.@timestamp, null)),
Esql.graph_time_min = min(case(Esql.event_type_case == "graph", Esql.@timestamp, null)),
Esql.event_count = count()
by Esql.azure_signinlogs_properties_session_id_coalesce, Esql.time_window_date_trunc
| eval
Esql.event_duration_minutes_date_diff = date_diff("minutes", Esql.@timestamp.min, Esql.@timestamp.max),
Esql.event_signin_to_graph_delay_minutes_date_diff = date_diff("minutes", Esql.signin_time_min, Esql.graph_time_min)
| where
Esql.event_type_case_count_distinct > 1 and
Esql.source_ip_count_distinct > 1 and
Esql.event_duration_minutes_date_diff <= 5 and
Esql.signin_time_min is not null and
Esql.graph_time_min is not null and
Esql.event_signin_to_graph_delay_minutes_date_diff >= 0
Install detection rules in Elastic Security
Detect Microsoft Entra ID Suspicious Session Reuse to Graph Access 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(opens in a new tab or window).