Techniques
Sample rules
Microsoft Entra ID Session Reuse with Suspicious Graph Access
- source: elastic
- technicques:
- T1078
- T1550
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 within a short time window. This may indicate the use of a stolen refresh/access token or session cookie to impersonate the user and interact with Microsoft services.
Detection logic
FROM logs-azure.*
| 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
session_id = COALESCE(azure.signinlogs.properties.session_id, azure.graphactivitylogs.properties.c_sid),
user_id = COALESCE(azure.signinlogs.properties.user_id, azure.graphactivitylogs.properties.user_principal_object_id),
client_id = COALESCE(azure.signinlogs.properties.app_id, azure.graphactivitylogs.properties.app_id),
source_ip = source.ip,
event_time = @timestamp,
event_type = CASE(
event.dataset == "azure.signinlogs", "signin",
event.dataset == "azure.graphactivitylogs", "graph",
"other"
),
time_window = DATE_TRUNC(5 minutes, @timestamp)
| KEEP session_id, source_ip, event_time, event_type, time_window, user_id, client_id
| STATS
user_id = VALUES(user_id),
session_id = VALUES(session_id),
source_ip_list = VALUES(source_ip),
source_ip_count = COUNT_DISTINCT(source_ip),
client_id_list = VALUES(client_id),
application_count = COUNT_DISTINCT(client_id),
event_type_list = VALUES(event_type),
event_type_count = COUNT_DISTINCT(event_type),
event_start = MIN(event_time),
event_end = MAX(event_time),
signin_time = MIN(CASE(event_type == "signin", event_time, NULL)),
graph_time = MIN(CASE(event_type == "graph", event_time, NULL)),
document_count = COUNT()
BY session_id, time_window
| EVAL
duration_minutes = DATE_DIFF("minutes", event_start, event_end),
signin_to_graph_delay_minutes = DATE_DIFF("minutes", signin_time, graph_time)
| WHERE
event_type_count > 1 AND
source_ip_count > 1 AND
duration_minutes <= 5 AND
signin_time IS NOT NULL AND
graph_time IS NOT NULL AND
signin_to_graph_delay_minutes >= 0