Suspicious Microsoft 365 UserLoggedIn via OAuth Code
Identifies sign-ins on behalf of a principal user to the Microsoft Graph API from multiple IPs using the Microsoft Authentication Broker or Visual Studio Code application. This behavior may indicate an adversary using a phished OAuth refresh token.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2025/05/01"
3integration = ["o365"]
4maturity = "production"
5updated_date = "2025/05/01"
6min_stack_version = "8.17.0"
7min_stack_comments = "Elastic ES|QL values aggregation is more performant in 8.16.5 and above."
8
9[rule]
10author = ["Elastic"]
11description = """
12Identifies sign-ins on behalf of a principal user to the Microsoft Graph API from multiple IPs using the Microsoft
13Authentication Broker or Visual Studio Code application. This behavior may indicate an adversary using a phished OAuth
14refresh token.
15"""
16from = "now-1h"
17language = "esql"
18license = "Elastic License v2"
19name = "Suspicious Microsoft 365 UserLoggedIn via OAuth Code"
20note = """## Triage and analysis
21
22### Investigating Suspicious Microsoft 365 UserLoggedIn via OAuth Code
23
24### Possible Investigation Steps:
25
26- `o365.audit.UserId`: The identity value the application is acting on behalf of principal user.
27- `unique_ips`: Analyze the list of unique IP addresses used within the 30-minute window. Determine whether these originate from different geographic regions, cloud providers, or anonymizing infrastructure (e.g., Tor or VPNs).
28- `target_time_window`: Use the truncated time window to pivot into raw events to reconstruct the full sequence of resource access events, including exact timestamps and service targets.
29- `azure.auditlogs` to check for device join or registration events around the same timeframe.
30- `azure.identityprotection` to identify correlated risk detections, such as anonymized IP access or token replay.
31- Any additional sign-ins from the `ips` involved, even outside the broker, to determine if tokens have been reused elsewhere.
32
33### False Positive Analysis
34
35- Developers or IT administrators working across environments may also produce similar behavior.
36
37### Response and Remediation
38
39- If confirmed unauthorized, revoke all refresh tokens for the affected user and remove any devices registered during this session.
40- Notify the user and determine whether the device join or authentication activity was expected.
41- Audit Conditional Access and broker permissions (`29d9ed98-a469-4536-ade2-f981bc1d605e`) to ensure policies enforce strict access controls.
42- Consider blocking token-based reauthentication to Microsoft Graph and DRS from suspicious locations or user agents.
43- Continue monitoring for follow-on activity like lateral movement or privilege escalation.
44"""
45references = [
46 "https://www.volexity.com/blog/2025/04/22/phishing-for-codes-russian-threat-actors-target-microsoft-365-oauth-workflows/",
47 "https://github.com/dirkjanm/ROADtools",
48 "https://dirkjanm.io/phishing-for-microsoft-entra-primary-refresh-tokens/",
49]
50risk_score = 73
51rule_id = "36188365-f88f-4f70-8c1d-0b9554186b9c"
52setup = """## Setup
53
54The Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.
55"""
56severity = "high"
57tags = [
58 "Domain: Cloud",
59 "Data Source: Microsoft 365",
60 "Use Case: Identity and Access Audit",
61 "Use Case: Threat Detection",
62 "Resources: Investigation Guide",
63 "Tactic: Defense Evasion",
64]
65timestamp_override = "event.ingested"
66type = "esql"
67
68query = '''
69from logs-o365.audit-default*
70| 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
72
73 // filter for successful logon to Microsoft Graph and from the Microsoft Authentication Broker or Visual Studio Code
74 o365.audit.ApplicationId in ("aebc6443-996d-45c2-90f0-388ff96faa56", "29d9ed98-a469-4536-ade2-f981bc1d605e") and
75 o365.audit.ObjectId in ("00000003-0000-0000-c000-000000000000")
76
77// keep relevant fields only
78| 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
79
80// 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)
82
83// split time to 30 minutes intervals
84| eval target_time_window = DATE_TRUNC(30 minutes, @timestamp)
85
86// 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
88
89// 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
90| where unique_ips >= 2 and is_oauth_authorize > 0 and is_oauth_token > 0
91'''
92
93
94[[rule.threat]]
95framework = "MITRE ATT&CK"
96[[rule.threat.technique]]
97id = "T1550"
98name = "Use Alternate Authentication Material"
99reference = "https://attack.mitre.org/techniques/T1550/"
100[[rule.threat.technique.subtechnique]]
101id = "T1550.001"
102name = "Application Access Token"
103reference = "https://attack.mitre.org/techniques/T1550/001/"
104
105
106
107[rule.threat.tactic]
108id = "TA0005"
109name = "Defense Evasion"
110reference = "https://attack.mitre.org/tactics/TA0005/"
Triage and analysis
Investigating Suspicious Microsoft 365 UserLoggedIn via OAuth Code
Possible Investigation Steps:
o365.audit.UserId
: The identity value the application is acting on behalf of principal user.unique_ips
: Analyze the list of unique IP addresses used within the 30-minute window. Determine whether these originate from different geographic regions, cloud providers, or anonymizing infrastructure (e.g., Tor or VPNs).target_time_window
: Use the truncated time window to pivot into raw events to reconstruct the full sequence of resource access events, including exact timestamps and service targets.azure.auditlogs
to check for device join or registration events around the same timeframe.azure.identityprotection
to identify correlated risk detections, such as anonymized IP access or token replay.- Any additional sign-ins from the
ips
involved, even outside the broker, to determine if tokens have been reused elsewhere.
False Positive Analysis
- Developers or IT administrators working across environments may also produce similar behavior.
Response and Remediation
- If confirmed unauthorized, revoke all refresh tokens for the affected user and remove any devices registered during this session.
- Notify the user and determine whether the device join or authentication activity was expected.
- Audit Conditional Access and broker permissions (
29d9ed98-a469-4536-ade2-f981bc1d605e
) to ensure policies enforce strict access controls. - Consider blocking token-based reauthentication to Microsoft Graph and DRS from suspicious locations or user agents.
- Continue monitoring for follow-on activity like lateral movement or privilege escalation.
References
Related rules
- Suspicious Activity via Auth Broker On-Behalf-of Principal User
- Attempts to Brute Force a Microsoft 365 User Account
- Microsoft 365 OAuth Redirect to Device Registration for User Principal
- Microsoft Entra ID Protection Anonymized IP Risk Detection
- Microsoft 365 OAuth Phishing via Visual Studio Code Client