M365 Identity OAuth Flow by First-Party Microsoft App from Multiple IPs
Identifies sign-ins on behalf of a principal user to the Microsoft Graph or legacy Azure AD API from multiple IPs using first-party Microsoft applications from the FOCI (Family of Client IDs) group. Developer tools like Azure CLI, VSCode, and Azure PowerShell accessing these resources from multiple IPs are flagged, along with any FOCI application accessing the deprecated Windows Azure Active Directory from multiple IPs. This behavior may indicate an adversary using a phished OAuth authorization code or refresh token, as seen in attacks like ConsentFix where attackers steal localhost OAuth codes and replay them from attacker infrastructure.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2025/05/01"
3integration = ["o365"]
4maturity = "production"
5updated_date = "2026/09/18"
6
7[rule]
8author = ["Elastic"]
9description = """
10Identifies sign-ins on behalf of a principal user to the Microsoft Graph or legacy Azure AD API from multiple IPs using
11first-party Microsoft applications from the FOCI (Family of Client IDs) group. Developer tools like Azure CLI, VSCode,
12and Azure PowerShell accessing these resources from multiple IPs are flagged, along with any FOCI application accessing
13the deprecated Windows Azure Active Directory from multiple IPs. This behavior may indicate an adversary using a phished
14OAuth authorization code or refresh token, as seen in attacks like ConsentFix where attackers steal localhost OAuth
15codes and replay them from attacker infrastructure.
16"""
17from = "now-60m"
18interval = "59m"
19language = "esql"
20license = "Elastic License v2"
21name = "M365 Identity OAuth Flow by First-Party Microsoft App from Multiple IPs"
22note = """## Triage and analysis
23
24### Investigating M365 Identity OAuth Flow by First-Party Microsoft App from Multiple IPs
25
26This rule detects when the same user authenticates to Microsoft Graph or legacy Azure AD using FOCI applications from multiple IP addresses within a 30-minute window. This pattern is a strong indicator of OAuth code/token theft attacks like ConsentFix, where the victim completes the OAuth authorize flow on their device (first IP), and the attacker exchanges the stolen authorization code for tokens from their infrastructure (second IP).
27
28The rule aggregates events by user, application, and resource, requiring both `OAuth2:Authorize` and `OAuth2:Token` requests from at least 2 different IPs to fire - this indicates the code was generated on one IP and exchanged on another.
29
30### Possible investigation steps
31
32- Review `o365.audit.UserId` to identify the affected user and determine if they are a high-value target.
33- Analyze `Esql.source_ip_values` to see all unique IP addresses used within the 30-minute window. Determine whether these originate from different geographic regions, cloud providers (AWS, Azure, GCP), or anonymizing infrastructure (Tor, VPNs).
34- Use `Esql.time_window_date_trunc` to pivot into raw events and reconstruct the full sequence of resource access events with exact timestamps.
35- Check `Esql.source_as_organization_name_values` for unfamiliar ASN organizations that may indicate attacker infrastructure.
36- Review `Esql.o365_audit_ApplicationId_values` to confirm which first-party application was used.
37- Pivot to `azure.auditlogs` to check for device join or registration events around the same timeframe, which may indicate persistence attempts.
38- Correlate with `azure.identityprotection` to identify related risk detections such as anonymized IP access or token replay.
39- Search for additional sign-ins from the IPs involved across other users to determine if this is part of a broader campaign.
40
41### False positive analysis
42
43- Developers or IT administrators working across environments (office, home, cloud VMs) may produce similar behavior.
44- Users on VPN who switch servers or traveling between networks may show multiple IPs.
45- Mobile users moving between cellular and WiFi networks during the time window.
46- Consider correlating with device compliance status to distinguish managed vs. unmanaged access.
47
48### Response and remediation
49
50- If confirmed unauthorized, immediately revoke all refresh tokens for the affected user via Entra ID.
51- Remove any devices registered during this session by checking `azure.auditlogs` for `Add device` events.
52- Notify the user and determine whether they may have shared an OAuth code via phishing.
53- Block the attacker IPs at the perimeter and add to threat intel feeds.
54- Implement Conditional Access policies to restrict OAuth flows for these applications to compliant devices and approved locations.
55- Monitor for follow-on activity like lateral movement, privilege escalation, or data exfiltration via Graph API.
56"""
57references = [
58 "https://www.volexity.com/blog/2025/04/22/phishing-for-codes-russian-threat-actors-target-microsoft-365-oauth-workflows/",
59 "https://github.com/dirkjanm/ROADtools",
60 "https://dirkjanm.io/phishing-for-microsoft-entra-primary-refresh-tokens/",
61 "https://pushsecurity.com/blog/consentfix",
62 "https://github.com/secureworks/family-of-client-ids-research",
63]
64risk_score = 73
65rule_id = "36188365-f88f-4f70-8c1d-0b9554186b9c"
66setup = """## Setup
67
68The Office 365 Logs Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.
69"""
70severity = "high"
71tags = [
72 "Domain: Cloud",
73 "Domain: Email",
74 "Domain: Identity",
75 "Data Source: Microsoft 365",
76 "Data Source: Microsoft 365 Audit Logs",
77 "Use Case: Identity and Access Audit",
78 "Use Case: Threat Detection",
79 "Resources: Investigation Guide",
80 "Tactic: Defense Evasion",
81 "Noise: Low",
82 "Performance: Normal",
83 "Rule Type: ES|QL",
84 "Platform: Microsoft 365",
85 "Domain: SaaS",
86]
87timestamp_override = "event.ingested"
88type = "esql"
89
90query = '''
91from logs-o365.audit-*
92| where
93 data_stream.dataset == "o365.audit" and
94 event.action == "UserLoggedIn" and
95 source.ip is not null and
96 o365.audit.UserId is not null and
97 o365.audit.ApplicationId is not null and
98 o365.audit.UserType in ("0", "2", "3", "10") and
99 (
100 /* Developer tools accessing Graph OR Legacy AAD */
101 (
102 o365.audit.ApplicationId in (
103 "aebc6443-996d-45c2-90f0-388ff96faa56",
104 "29d9ed98-a469-4536-ade2-f981bc1d605e",
105 "04b07795-8ddb-461a-bbee-02f9e1bf7b46",
106 "1950a258-227b-4e31-a9cf-717495945fc2"
107 ) and
108 o365.audit.ObjectId in (
109 "00000003-0000-0000-c000-000000000000",
110 "00000002-0000-0000-c000-000000000000"
111 )
112 ) or
113 /* Any FOCI app accessing Legacy AAD only */
114 (
115 o365.audit.ApplicationId in (
116 "00b41c95-dab0-4487-9791-b9d2c32c80f2",
117 "1fec8e78-bce4-4aaf-ab1b-5451cc387264",
118 "26a7ee05-5602-4d76-a7ba-eae8b7b67941",
119 "27922004-5251-4030-b22d-91ecd9a37ea4",
120 "4813382a-8fa7-425e-ab75-3b753aab3abb",
121 "ab9b8c07-8f02-4f72-87fa-80105867a763",
122 "d3590ed6-52b3-4102-aeff-aad2292ab01c",
123 "872cd9fa-d31f-45e0-9eab-6e460a02d1f1",
124 "af124e86-4e96-495a-b70a-90f90ab96707",
125 "2d7f3606-b07d-41d1-b9d2-0d0c9296a6e8",
126 "844cca35-0656-46ce-b636-13f48b0eecbd",
127 "87749df4-7ccf-48f8-aa87-704bad0e0e16",
128 "cf36b471-5b44-428c-9ce7-313bf84528de",
129 "0ec893e0-5785-4de6-99da-4ed124e5296c",
130 "22098786-6e16-43cc-a27d-191a01a1e3b5",
131 "4e291c71-d680-4d0e-9640-0a3358e31177",
132 "57336123-6e14-4acc-8dcf-287b6088aa28",
133 "57fcbcfa-7cee-4eb1-8b25-12d2030b4ee0",
134 "66375f6b-983f-4c2c-9701-d680650f588f",
135 "9ba1a5c7-f17a-4de9-a1f1-6178c8d51223",
136 "a40d7d7d-59aa-447e-a655-679a4107e548",
137 "a569458c-7f2b-45cb-bab9-b7dee514d112",
138 "b26aadf8-566f-4478-926f-589f601d9c74",
139 "c0d2a505-13b8-4ae0-aa9e-cddd5eab0b12",
140 "d326c1ce-6cc6-4de2-bebc-4591e5e13ef0",
141 "e9c51622-460d-4d3d-952d-966a5b1da34c",
142 "eb539595-3fe1-474e-9c1d-feb3625d1be5",
143 "ecd6b820-32c2-49b6-98a6-444530e5a77a",
144 "f05ff7c9-f75a-4acd-a3b5-f4b6a870245d",
145 "f44b1140-bc5e-48c6-8dc0-5cf5a53c0e34",
146 "be1918be-3fe3-4be9-b32b-b542fc27f02e",
147 "cab96880-db5b-4e15-90a7-f3f1d62ffe39",
148 "d7b530a4-7680-4c23-a8bf-c52c121d2e87",
149 "dd47d17a-3194-4d86-bfd5-c6ae6f5651e3",
150 "e9b154d0-7658-433b-bb25-6b8e0a8a7c59"
151 ) and
152 o365.audit.ObjectId == "00000002-0000-0000-c000-000000000000"
153 )
154 )
155| eval
156 Esql.time_window_date_trunc = date_trunc(30 minutes, @timestamp),
157 Esql.oauth_authorize_user_id_case = case(
158 o365.audit.ExtendedProperties.RequestType == "OAuth2:Authorize" and o365.audit.ExtendedProperties.ResultStatusDetail == "Redirect",
159 o365.audit.UserId,
160 null
161 ),
162 Esql.oauth_token_user_id_case = case(
163 o365.audit.ExtendedProperties.RequestType == "OAuth2:Token",
164 o365.audit.UserId,
165 null
166 )
167| stats
168 Esql.source_ip_count_distinct = count_distinct(source.ip),
169 Esql.source_ip_values = values(source.ip),
170 Esql.o365_audit_ApplicationId_values = values(o365.audit.ApplicationId),
171 Esql.source_as_organization_name_values = values(source.`as`.organization.name),
172 Esql.oauth_token_count_distinct = count_distinct(Esql.oauth_token_user_id_case),
173 Esql.oauth_authorize_count_distinct = count_distinct(Esql.oauth_authorize_user_id_case)
174 by
175 o365.audit.UserId,
176 Esql.time_window_date_trunc,
177 o365.audit.ApplicationId,
178 o365.audit.ObjectId
179| keep
180 Esql.time_window_date_trunc,
181 Esql.source_ip_values,
182 Esql.source_ip_count_distinct,
183 Esql.o365_audit_ApplicationId_values,
184 Esql.source_as_organization_name_values,
185 Esql.oauth_token_count_distinct,
186 Esql.oauth_authorize_count_distinct
187| where
188 Esql.source_ip_count_distinct >= 2 and
189 Esql.oauth_token_count_distinct > 0 and
190 Esql.oauth_authorize_count_distinct > 0
191'''
192
193
194[[rule.threat]]
195framework = "MITRE ATT&CK"
196
197[[rule.threat.technique]]
198id = "T1550"
199name = "Use Alternate Authentication Material"
200reference = "https://attack.mitre.org/techniques/T1550/"
201
202[[rule.threat.technique.subtechnique]]
203id = "T1550.001"
204name = "Application Access Token"
205reference = "https://attack.mitre.org/techniques/T1550/001/"
206
207[rule.threat.tactic]
208id = "TA0005"
209name = "Defense Evasion"
210reference = "https://attack.mitre.org/tactics/TA0005/"
211
212[[rule.threat]]
213framework = "MITRE ATT&CK"
214
215[[rule.threat.technique]]
216id = "T1528"
217name = "Steal Application Access Token"
218reference = "https://attack.mitre.org/techniques/T1528/"
219
220[rule.threat.tactic]
221id = "TA0006"
222name = "Credential Access"
223reference = "https://attack.mitre.org/tactics/TA0006/"
224
225[[rule.threat]]
226framework = "MITRE ATT&CK"
227
228[[rule.threat.technique]]
229id = "T1078"
230name = "Valid Accounts"
231reference = "https://attack.mitre.org/techniques/T1078/"
232
233[[rule.threat.technique.subtechnique]]
234id = "T1078.004"
235name = "Cloud Accounts"
236reference = "https://attack.mitre.org/techniques/T1078/004/"
237
238[[rule.threat.technique]]
239id = "T1566"
240name = "Phishing"
241reference = "https://attack.mitre.org/techniques/T1566/"
242
243[[rule.threat.technique.subtechnique]]
244id = "T1566.002"
245name = "Spearphishing Link"
246reference = "https://attack.mitre.org/techniques/T1566/002/"
247
248[rule.threat.tactic]
249id = "TA0001"
250name = "Initial Access"
251reference = "https://attack.mitre.org/tactics/TA0001/"
Triage and analysis
Investigating M365 Identity OAuth Flow by First-Party Microsoft App from Multiple IPs
This rule detects when the same user authenticates to Microsoft Graph or legacy Azure AD using FOCI applications from multiple IP addresses within a 30-minute window. This pattern is a strong indicator of OAuth code/token theft attacks like ConsentFix, where the victim completes the OAuth authorize flow on their device (first IP), and the attacker exchanges the stolen authorization code for tokens from their infrastructure (second IP).
The rule aggregates events by user, application, and resource, requiring both OAuth2:Authorize and OAuth2:Token requests from at least 2 different IPs to fire - this indicates the code was generated on one IP and exchanged on another.
Possible investigation steps
- Review
o365.audit.UserIdto identify the affected user and determine if they are a high-value target. - Analyze
Esql.source_ip_valuesto see all unique IP addresses used within the 30-minute window. Determine whether these originate from different geographic regions, cloud providers (AWS, Azure, GCP), or anonymizing infrastructure (Tor, VPNs). - Use
Esql.time_window_date_truncto pivot into raw events and reconstruct the full sequence of resource access events with exact timestamps. - Check
Esql.source_as_organization_name_valuesfor unfamiliar ASN organizations that may indicate attacker infrastructure. - Review
Esql.o365_audit_ApplicationId_valuesto confirm which first-party application was used. - Pivot to
azure.auditlogsto check for device join or registration events around the same timeframe, which may indicate persistence attempts. - Correlate with
azure.identityprotectionto identify related risk detections such as anonymized IP access or token replay. - Search for additional sign-ins from the IPs involved across other users to determine if this is part of a broader campaign.
False positive analysis
- Developers or IT administrators working across environments (office, home, cloud VMs) may produce similar behavior.
- Users on VPN who switch servers or traveling between networks may show multiple IPs.
- Mobile users moving between cellular and WiFi networks during the time window.
- Consider correlating with device compliance status to distinguish managed vs. unmanaged access.
Response and remediation
- If confirmed unauthorized, immediately revoke all refresh tokens for the affected user via Entra ID.
- Remove any devices registered during this session by checking
azure.auditlogsforAdd deviceevents. - Notify the user and determine whether they may have shared an OAuth code via phishing.
- Block the attacker IPs at the perimeter and add to threat intel feeds.
- Implement Conditional Access policies to restrict OAuth flows for these applications to compliant devices and approved locations.
- Monitor for follow-on activity like lateral movement, privilege escalation, or data exfiltration via Graph API.
References
Related rules
- M365 Identity Device Code Grant with Unusual User and ASN
- M365 Identity OAuth ROPC Grant via Legacy Authentication Client
- M365 Identity Unusual SSO Authentication Errors for User
- M365 or Entra ID Identity Sign-in from a Suspicious Source
- Entra ID Kali365 Default User-Agent Detected