Entra ID OAuth Device Code Sign-in to Azure AD Graph Enumeration
Correlates a successful Entra ID device-code sign-in to the legacy Azure AD Graph audience (00000002-0000-0000-c000-000000000000) from an unmanaged device with directory enumeration against graph.windows.net by the same user within a short window. Device-code phishing is the dominant OAuth phishing variant against Microsoft tenants: the adversary initiates the flow, relays the user-facing code to the victim, and on redemption walks away with an access or refresh token bound to the targeted resource without ever handling the user's password or MFA factor. When the redeemed audience is AAD Graph and the redeeming device is unmanaged, the follow-on Graph traffic is the compromised cloud account being used by the attacker, not by the user. This rule fires when that token is immediately turned around against the directory under the same identity to read user, group, service principal, application, role assignment, directory object, policy, OAuth permission grant, or tenant detail collections.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2026/05/22"
3integration = ["azure"]
4maturity = "production"
5updated_date = "2026/09/18"
6
7[rule]
8author = ["Elastic"]
9description = """
10Correlates a successful Entra ID device-code sign-in to the legacy Azure AD Graph audience
11(00000002-0000-0000-c000-000000000000) from an unmanaged device with directory enumeration against graph.windows.net by
12the same user within a short window. Device-code phishing is the dominant OAuth phishing variant against Microsoft
13tenants: the adversary initiates the flow, relays the user-facing code to the victim, and on redemption walks away with
14an access or refresh token bound to the targeted resource without ever handling the user's password or MFA factor. When
15the redeemed audience is AAD Graph and the redeeming device is unmanaged, the follow-on Graph traffic is the compromised
16cloud account being used by the attacker, not by the user. This rule fires when that token is immediately turned around
17against the directory under the same identity to read user, group, service principal, application, role assignment,
18directory object, policy, OAuth permission grant, or tenant detail collections.
19"""
20false_positives = [
21 """
22 Authorized red team or audit activity (ROADrecon, ROADtools, AADInternals, roadtx). Document the engagement window
23 and add exceptions on the calling user.
24 """,
25 """
26 A developer or operator legitimately running first-party tooling under the device-code flow that then enumerates
27 directory objects during onboarding or troubleshooting. Validate the calling app and source IP and exclude as
28 appropriate.
29 """,
30]
31from = "now-9m"
32index = ["logs-azure.signinlogs-*", "logs-azure.aadgraphactivitylogs-*"]
33language = "eql"
34license = "Elastic License v2"
35name = "Entra ID OAuth Device Code Sign-in to Azure AD Graph Enumeration"
36note = """## Triage and analysis
37
38### Investigating Entra ID OAuth Device Code Sign-in to Azure AD Graph Enumeration
39
40Device-code phishing redeems an OAuth access token directly into the adversary's hands without
41ever touching the victim's password or MFA factor. When the redemption targets the legacy AAD
42Graph audience from an unmanaged device, the resulting token is overwhelmingly used to drive
43directory recon under the compromised identity. ROADrecon / ROADtools, AADInternals
44(`Get-AADIntTenantDetails`, `Get-AADIntUsers`), and manual `roadtx` flows all match this shape.
45
46### Possible investigation steps
47
48- Confirm the sign-in shape.
49 - `azure.signinlogs.properties.authentication_protocol` is `deviceCode`.
50 - `azure.signinlogs.properties.resource_id` is `00000002-0000-0000-c000-000000000000` (legacy AAD Graph audience).
51 - `azure.signinlogs.properties.device_detail.is_managed` is `false`.
52- Identify the calling client used to drive the device-code grant.
53 - `azure.signinlogs.properties.app_id`, `azure.signinlogs.properties.app_display_name`.
54 - FOCI / pre-consented Microsoft clients (Teams, Office, Azure CLI, Azure PowerShell) are the canonical ride-along clients for device-code phishing because they bypass app consent.
55- Review source posture for the redemption and the Graph follow-on independently.
56 - `source.ip`, `source.as.organization.name`, `source.geo.country_name`. Residential / VPS / anonymising-network egress raises priority.
57 - A code redeemed from one IP and Graph driven from another is a strong adversary-in-the-middle signal: the user clicked, the attacker is now driving the session.
58- Review what was queried on the Graph side.
59 - `url.path` on the second event. `applicationRefs`, `eligibleRoleAssignments`, and `directoryObjects` casts (`$/Microsoft.DirectoryServices.ServicePrincipal`) are the textbook ROADrecon signature; `tenantDetails` from an `AADInternals` user-agent is the AADInternals signature.
60- Check the API version on the Graph call.
61 - `azure.aadgraphactivitylogs.properties.api_version`. `1.61-internal` is a strong tooling indicator and returns data the public surface withholds (Conditional Access policies, MFA configuration on user objects).
62- Pivot to surrounding sign-ins for the same user. Other device-code redemptions to Microsoft Graph, Azure Resource Manager, or Exchange in the same window suggest the attacker is multi-homing the token harvest.
63- Confirm the activity is not attributable to authorized testing before treating as malicious.
64
65### Response and remediation
66
67- Revoke refresh tokens and active sessions for the compromised user.
68 - `POST /v1.0/users/{id}/revokeSignInSessions`.
69- Temporarily disable the user if the alert is high-confidence or you need to halt further activity while investigation continues.
70 - `PATCH /v1.0/users/{id}` with body `{"accountEnabled": false}`.
71- Check for device registrations created by the user during or around the burst window and remove rogue devices.
72 - `GET /v1.0/users/{id}/registeredDevices` and `GET /v1.0/users/{id}/ownedDevices`, then `DELETE /v1.0/devices/{deviceObjectId}`.
73 - Do this BEFORE session revocation: device-bound PRTs survive `revokeSignInSessions`.
74- If the calling application has no legitimate AAD Graph dependency, block further use by that app.
75 - `PATCH /beta/applications/{id}` with body `{"authenticationBehaviors": {"blockAzureADGraphAccess": true}}`.
76 - This property lives on the Graph beta endpoint, not v1.0.
77- Apply Conditional Access targeting the device-code grant: require a managed / compliant device or block the device-code grant outside of explicitly approved app + user populations.
78"""
79references = [
80 "https://github.com/dirkjanm/ROADtools",
81 "https://github.com/Gerenios/AADInternals",
82 "https://learn.microsoft.com/en-us/graph/migrate-azure-ad-graph-overview",
83]
84risk_score = 73
85rule_id = "aa04377a-19b5-4940-952f-aad173790d23"
86setup = """#### Microsoft Entra ID Sign-in Logs and Azure AD Graph Activity Logs
87Requires both data streams ingested via the Elastic Azure integration:
88- Microsoft Entra ID sign-in logs into `logs-azure.signinlogs-*` (enable the `SignInLogs` diagnostic-settings category on Entra ID).
89- Azure AD Graph Activity Logs into `logs-azure.aadgraphactivitylogs-*` (enable the `AzureADGraphActivityLogs` diagnostic-settings category on Entra ID).
90"""
91severity = "high"
92tags = [
93 "Domain: Cloud",
94 "Domain: Identity",
95 "Data Source: Azure",
96 "Data Source: Microsoft Entra ID",
97 "Data Source: Microsoft Entra ID Sign-In Logs",
98 "Data Source: Azure AD Graph",
99 "Data Source: Azure AD Graph Activity Logs",
100 "Use Case: Identity and Access Audit",
101 "Use Case: Threat Detection",
102 "Tactic: Credential Access",
103 "Tactic: Initial Access",
104 "Tactic: Discovery",
105 "Resources: Investigation Guide",
106 "Noise: Unknown",
107 "Performance: Fast",
108 "Profile: Recommended",
109 "Threat: Device Code Phishing",
110 "Rule Type: Event Correlation (EQL)",
111 "Platform: Entra ID",
112 "Platform: Azure",
113]
114timestamp_override = "event.ingested"
115type = "eql"
116
117query = '''
118sequence by user.id, azure.tenant_id with maxspan=5m
119[authentication where
120 data_stream.dataset == "azure.signinlogs" and
121 event.outcome == "success" and
122 azure.signinlogs.properties.authentication_protocol == "deviceCode" and
123 azure.signinlogs.properties.device_detail.is_managed == false and
124 azure.signinlogs.properties.resource_id == "00000002-0000-0000-c000-000000000000"]
125[web where
126 data_stream.dataset == "azure.aadgraphactivitylogs" and
127 url.path : (
128 "*/users*",
129 "*/groups*",
130 "*/servicePrincipals*",
131 "*/applications*",
132 "*/applicationRefs*",
133 "*/devices*",
134 "*/directoryRoles*",
135 "*/roleAssignments*",
136 "*/eligibleRoleAssignments*",
137 "*/roleDefinitions*",
138 "*/directoryObjects*",
139 "*/policies*",
140 "*/oauth2PermissionGrants*",
141 "*/administrativeUnits*",
142 "*/tenantDetails*",
143 "*/directorySettingTemplates*",
144 "*/me*"
145 )]
146'''
147
148
149[[rule.threat]]
150framework = "MITRE ATT&CK"
151[[rule.threat.technique]]
152id = "T1528"
153name = "Steal Application Access Token"
154reference = "https://attack.mitre.org/techniques/T1528/"
155
156
157[rule.threat.tactic]
158id = "TA0006"
159name = "Credential Access"
160reference = "https://attack.mitre.org/tactics/TA0006/"
161[[rule.threat]]
162framework = "MITRE ATT&CK"
163[[rule.threat.technique]]
164id = "T1078"
165name = "Valid Accounts"
166reference = "https://attack.mitre.org/techniques/T1078/"
167[[rule.threat.technique.subtechnique]]
168id = "T1078.004"
169name = "Cloud Accounts"
170reference = "https://attack.mitre.org/techniques/T1078/004/"
171
172
173
174[rule.threat.tactic]
175id = "TA0001"
176name = "Initial Access"
177reference = "https://attack.mitre.org/tactics/TA0001/"
178[[rule.threat]]
179framework = "MITRE ATT&CK"
180[[rule.threat.technique]]
181id = "T1069"
182name = "Permission Groups Discovery"
183reference = "https://attack.mitre.org/techniques/T1069/"
184[[rule.threat.technique.subtechnique]]
185id = "T1069.003"
186name = "Cloud Groups"
187reference = "https://attack.mitre.org/techniques/T1069/003/"
188
189
190[[rule.threat.technique]]
191id = "T1087"
192name = "Account Discovery"
193reference = "https://attack.mitre.org/techniques/T1087/"
194[[rule.threat.technique.subtechnique]]
195id = "T1087.004"
196name = "Cloud Account"
197reference = "https://attack.mitre.org/techniques/T1087/004/"
198
199
200[[rule.threat.technique]]
201id = "T1526"
202name = "Cloud Service Discovery"
203reference = "https://attack.mitre.org/techniques/T1526/"
204
205
206[rule.threat.tactic]
207id = "TA0007"
208name = "Discovery"
209reference = "https://attack.mitre.org/tactics/TA0007/"
210
211[rule.investigation_fields]
212field_names = [
213 "user.id",
214 "azure.tenant_id",
215 "azure.signinlogs.properties.user_principal_name",
216 "azure.signinlogs.properties.app_id",
217 "azure.signinlogs.properties.app_display_name",
218 "azure.signinlogs.properties.resource_id",
219 "azure.signinlogs.properties.authentication_protocol",
220 "azure.signinlogs.properties.device_detail.is_managed",
221 "azure.aadgraphactivitylogs.properties.app_id",
222 "azure.aadgraphactivitylogs.properties.api_version",
223 "url.path",
224 "user_agent.original",
225 "source.ip",
226 "source.as.organization.name",
227 "source.geo.country_name",
228]
Triage and analysis
Investigating Entra ID OAuth Device Code Sign-in to Azure AD Graph Enumeration
Device-code phishing redeems an OAuth access token directly into the adversary's hands without
ever touching the victim's password or MFA factor. When the redemption targets the legacy AAD
Graph audience from an unmanaged device, the resulting token is overwhelmingly used to drive
directory recon under the compromised identity. ROADrecon / ROADtools, AADInternals
(Get-AADIntTenantDetails, Get-AADIntUsers), and manual roadtx flows all match this shape.
Possible investigation steps
- Confirm the sign-in shape.
azure.signinlogs.properties.authentication_protocolisdeviceCode.azure.signinlogs.properties.resource_idis00000002-0000-0000-c000-000000000000(legacy AAD Graph audience).azure.signinlogs.properties.device_detail.is_managedisfalse.
- Identify the calling client used to drive the device-code grant.
azure.signinlogs.properties.app_id,azure.signinlogs.properties.app_display_name.- FOCI / pre-consented Microsoft clients (Teams, Office, Azure CLI, Azure PowerShell) are the canonical ride-along clients for device-code phishing because they bypass app consent.
- Review source posture for the redemption and the Graph follow-on independently.
source.ip,source.as.organization.name,source.geo.country_name. Residential / VPS / anonymising-network egress raises priority.- A code redeemed from one IP and Graph driven from another is a strong adversary-in-the-middle signal: the user clicked, the attacker is now driving the session.
- Review what was queried on the Graph side.
url.pathon the second event.applicationRefs,eligibleRoleAssignments, anddirectoryObjectscasts ($/Microsoft.DirectoryServices.ServicePrincipal) are the textbook ROADrecon signature;tenantDetailsfrom anAADInternalsuser-agent is the AADInternals signature.
- Check the API version on the Graph call.
azure.aadgraphactivitylogs.properties.api_version.1.61-internalis a strong tooling indicator and returns data the public surface withholds (Conditional Access policies, MFA configuration on user objects).
- Pivot to surrounding sign-ins for the same user. Other device-code redemptions to Microsoft Graph, Azure Resource Manager, or Exchange in the same window suggest the attacker is multi-homing the token harvest.
- Confirm the activity is not attributable to authorized testing before treating as malicious.
Response and remediation
- Revoke refresh tokens and active sessions for the compromised user.
POST /v1.0/users/{id}/revokeSignInSessions.
- Temporarily disable the user if the alert is high-confidence or you need to halt further activity while investigation continues.
PATCH /v1.0/users/{id}with body{"accountEnabled": false}.
- Check for device registrations created by the user during or around the burst window and remove rogue devices.
GET /v1.0/users/{id}/registeredDevicesandGET /v1.0/users/{id}/ownedDevices, thenDELETE /v1.0/devices/{deviceObjectId}.- Do this BEFORE session revocation: device-bound PRTs survive
revokeSignInSessions.
- If the calling application has no legitimate AAD Graph dependency, block further use by that app.
PATCH /beta/applications/{id}with body{"authenticationBehaviors": {"blockAzureADGraphAccess": true}}.- This property lives on the Graph beta endpoint, not v1.0.
- Apply Conditional Access targeting the device-code grant: require a managed / compliant device or block the device-code grant outside of explicitly approved app + user populations.
References
Related rules
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access
- Entra ID AiTM Phishing-Kit Chain Detected
- Entra ID Kali365 Default User-Agent Detected
- Entra ID OAuth Device Code Grant by Unusual User
- Entra ID OAuth PRT Issuance to Non-Managed Device Detected