22creation_date = " 2025/05/01"
33integration = [" o365" ]
44maturity = " production"
5- updated_date = " 2025/05/01 "
5+ updated_date = " 2025/06/25 "
66min_stack_version = " 8.17.0"
77min_stack_comments = " Elastic ES|QL values aggregation is more performant in 8.16.5 and above."
88
@@ -13,7 +13,8 @@ Identifies sign-ins on behalf of a principal user to the Microsoft Graph API fro
1313Authentication Broker or Visual Studio Code application. This behavior may indicate an adversary using a phished OAuth
1414refresh token.
1515"""
16- from = " now-1h"
16+ from = " now-60m"
17+ interval = " 59m"
1718language = " esql"
1819license = " Elastic License v2"
1920name = " Suspicious Microsoft 365 UserLoggedIn via OAuth Code"
@@ -56,7 +57,10 @@ The Office 365 Logs Fleet integration, Filebeat module, or similarly structured
5657severity = " high"
5758tags = [
5859 " Domain: Cloud" ,
60+ " Domain: Email" ,
61+ " Domain: Identity" ,
5962 " Data Source: Microsoft 365" ,
63+ " Data Source: Microsoft 365 Audit Logs" ,
6064 " Use Case: Identity and Access Audit" ,
6165 " Use Case: Threat Detection" ,
6266 " Resources: Investigation Guide" ,
@@ -66,9 +70,16 @@ timestamp_override = "event.ingested"
6670type = " esql"
6771
6872query = '''
69- from logs-o365.audit-default *
73+ from logs-o365.audit-*
7074| WHERE event.dataset == "o365.audit" and event.action == "UserLoggedIn" and
71- source.ip is not null and o365.audit.UserId is not null and o365.audit.ApplicationId is not null and o365.audit.UserType in ("0", "2", "3", "10") and
75+
76+ // ensure source, application and user are not null
77+ source.ip is not null and
78+ o365.audit.UserId is not null and
79+ o365.audit.ApplicationId is not null and
80+
81+ // filter for user principals that are not service accounts
82+ o365.audit.UserType in ("0", "2", "3", "10") and
7283
7384 // filter for successful logon to Microsoft Graph and from the Microsoft Authentication Broker or Visual Studio Code
7485 o365.audit.ApplicationId in ("aebc6443-996d-45c2-90f0-388ff96faa56", "29d9ed98-a469-4536-ade2-f981bc1d605e") and
@@ -78,13 +89,22 @@ from logs-o365.audit-default*
7889| keep @timestamp, o365.audit.UserId, source.ip, o365.audit.ApplicationId, o365.audit.ObjectId, o365.audit.ExtendedProperties.RequestType, source.as.organization.name, o365.audit.ExtendedProperties.ResultStatusDetail
7990
8091// case statements to track which are OAuth2 authorization request via redirect and which are related to OAuth2 code to token conversion
81- | eval oauth_authorize = case(o365.audit.ExtendedProperties.RequestType == "OAuth2:Authorize" and o365.audit.ExtendedProperties.ResultStatusDetail == "Redirect", o365.audit.UserId, null), oauth_token = case(o365.audit.ExtendedProperties.RequestType == "OAuth2:Token", o365.audit.UserId, null)
92+ | eval
93+ oauth_authorize = case(o365.audit.ExtendedProperties.RequestType == "OAuth2:Authorize" and o365.audit.ExtendedProperties.ResultStatusDetail == "Redirect", o365.audit.UserId, null),
94+ oauth_token = case(o365.audit.ExtendedProperties.RequestType == "OAuth2:Token", o365.audit.UserId, null)
8295
8396// split time to 30 minutes intervals
8497| eval target_time_window = DATE_TRUNC(30 minutes, @timestamp)
8598
8699// aggregate by principal, applicationId, objectId and time window
87- | stats unique_ips = COUNT_DISTINCT(source.ip), source_ips = VALUES(source.ip), appIds = VALUES(o365.audit.ApplicationId), asn = values(`source.as.organization.name`), is_oauth_token = COUNT_DISTINCT(oauth_token), is_oauth_authorize = COUNT_DISTINCT(oauth_authorize) by o365.audit.UserId, target_time_window, o365.audit.ApplicationId, o365.audit.ObjectId
100+ | stats
101+ unique_ips = COUNT_DISTINCT(source.ip),
102+ source_ips = VALUES(source.ip),
103+ appIds = VALUES(o365.audit.ApplicationId),
104+ asn = values(`source.as.organization.name`),
105+ is_oauth_token = COUNT_DISTINCT(oauth_token),
106+ is_oauth_authorize = COUNT_DISTINCT(oauth_authorize)
107+ by o365.audit.UserId, target_time_window, o365.audit.ApplicationId, o365.audit.ObjectId
88108
89109// filter for cases where the same appId is used by the same principal user to access the same object and from multiple addresses via OAuth2 token
90110| where unique_ips >= 2 and is_oauth_authorize > 0 and is_oauth_token > 0
0 commit comments