LoFP LoFP / this pattern may occur during legitimate device switching or roaming between networks (e.g., corporate to mobile). developers or power users leveraging multiple environments may also trigger this detection if session persistence spans ip ranges. still, this behavior is rare and warrants investigation when rapid ip switching and graph access are involved.

Techniques

Sample rules

Microsoft Entra ID Suspicious Session Reuse to Graph Access

Description

Identifies potential session hijacking or token replay in Microsoft Entra ID. This rule detects cases where a user signs in and subsequently accesses Microsoft Graph from a different IP address using the same session ID. This may indicate a successful OAuth phishing attack, session hijacking, or token replay attack, where an adversary has stolen a session cookie or refresh/access token and is impersonating the user from an alternate host or location.

Detection logic

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