Skip to content

Commit bcec34f

Browse files
authored
Merge branch 'main' into deprecate-deprecated-rules
2 parents 39dfa7a + fb03295 commit bcec34f

File tree

1 file changed

+26
-6
lines changed

1 file changed

+26
-6
lines changed

rules/integrations/o365/defense_evasion_microsoft_365_susp_oauth2_authorization.toml

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
creation_date = "2025/05/01"
33
integration = ["o365"]
44
maturity = "production"
5-
updated_date = "2025/05/01"
5+
updated_date = "2025/06/25"
66
min_stack_version = "8.17.0"
77
min_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
1313
Authentication Broker or Visual Studio Code application. This behavior may indicate an adversary using a phished OAuth
1414
refresh token.
1515
"""
16-
from = "now-1h"
16+
from = "now-60m"
17+
interval = "59m"
1718
language = "esql"
1819
license = "Elastic License v2"
1920
name = "Suspicious Microsoft 365 UserLoggedIn via OAuth Code"
@@ -56,7 +57,10 @@ The Office 365 Logs Fleet integration, Filebeat module, or similarly structured
5657
severity = "high"
5758
tags = [
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"
6670
type = "esql"
6771

6872
query = '''
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

Comments
 (0)