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

to-top