Azure AD Graph Access with Unusual Client and User
Identifies Azure AD Graph (graph.windows.net) requests where the combination of calling OAuth client ("azure.aadgraphactivitylogs.properties.app_id") and signed-in user ("user.id") has not been observed in the tenant in a historical window. A user appearing against AAD Graph under an OAuth client that has not previously authenticated that user is a sign of a FOCI swap, a phished refresh token being redeemed for a new client, or an adversary running tooling under a client identity the user does not normally use.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2026/05/22"
3integration = ["azure"]
4maturity = "production"
5updated_date = "2026/05/22"
6
7[rule]
8author = ["Elastic"]
9description = """
10Identifies Azure AD Graph (graph.windows.net) requests where the combination of calling OAuth client
11("azure.aadgraphactivitylogs.properties.app_id") and signed-in user ("user.id") has not been observed in the tenant in
12a historical window. A user appearing against AAD Graph under an OAuth client that has not previously authenticated
13that user is a sign of a FOCI swap, a phished refresh token being redeemed for a new client, or an adversary running
14tooling under a client identity the user does not normally use.
15"""
16false_positives = [
17 """
18 First-time use of a legitimate first-party or sanctioned client by the user (newly installed app, first sign-in to a
19 workload, fresh PowerShell module install). Validate by `azure.aadgraphactivitylogs.properties.app_id` and the
20 user's history.
21 """,
22 """
23 Authorized red team or audit activity. Add exceptions on the calling user after review.
24 """,
25]
26from = "now-9m"
27index = ["logs-azure.aadgraphactivitylogs-*"]
28language = "kuery"
29license = "Elastic License v2"
30name = "Azure AD Graph Access with Unusual Client and User"
31note = """## Triage and analysis
32
33### Investigating Azure AD Graph Access with Unusual Client and User
34
35A (client, user) pair appearing on AAD Graph for the first time in 14 days is a high-signal indicator. Legacy
36AAD Graph traffic is dominated by a small set of recognised first-party callers per user; new combinations
37suggest one of:
38
39- A FOCI refresh-token swap landed under a new client identity.
40- The user newly consented to (or was phished into consenting to) an OAuth application.
41- An adversary is using stolen tokens / credentials under a client identity the user does not normally use.
42
43### Possible investigation steps
44
45- Identify the calling client and confirm whether the app is sanctioned.
46 - `azure.aadgraphactivitylogs.properties.app_id`. Pivot to Azure portal → Enterprise Applications to check whether it is first-party, sanctioned third-party, or unfamiliar.
47- Identify the user and check the surrounding auth context.
48 - `user.id`, then pivot to sign-in logs (`logs-azure.signinlogs-*`) for the same user around the same time. Look for unusual sign-in geography, MFA bypass, or risky session signals.
49- Review source posture.
50 - `user_agent.original`, `source.ip`, `source.as.organization.name`. Residential / VPS / anonymising-network egress raises priority.
51- Review what was queried.
52 - `url.path`. Bulk recon or User-collection access via internal API versions raises triage priority.
53- Check tenant-wide blast radius for the client.
54 - Is the same client ID hitting AAD Graph for many other users? That pattern points to large-scale consent abuse rather than a single account compromise.
55- Confirm the activity is not attributable to authorized testing (red team engagement, penetration test, internal tooling validation) before treating as malicious.
56
57### Response and remediation
58
59- Revoke refresh tokens and active sessions for the calling user.
60 - `POST /v1.0/users/{id}/revokeSignInSessions`.
61- Temporarily disable the user if the alert is high-confidence or you need to halt further activity while investigation continues.
62 - `PATCH /v1.0/users/{id}` with body `{"accountEnabled": false}`.
63- If the client is not a sanctioned application, revoke its OAuth consent.
64 - `GET /v1.0/oauth2PermissionGrants?$filter=clientId eq '{servicePrincipalId}'`, then `DELETE /v1.0/oauth2PermissionGrants/{grantId}`.
65- Check for device registrations created by the user during or around the burst window and remove rogue devices.
66 - `GET /v1.0/users/{id}/registeredDevices` and `GET /v1.0/users/{id}/ownedDevices`, then `DELETE /v1.0/devices/{deviceObjectId}`.
67 - Do this BEFORE session revocation: device-bound PRTs survive `revokeSignInSessions`.
68- If the calling application has no legitimate AAD Graph dependency, block further use by that app.
69 - `PATCH /beta/applications/{id}` with body `{"authenticationBehaviors": {"blockAzureADGraphAccess": true}}`.
70 - This property lives on the Graph beta endpoint, not v1.0.
71"""
72references = [
73 "https://github.com/secureworks/family-of-client-ids-research",
74 "https://learn.microsoft.com/en-us/graph/migrate-azure-ad-graph-overview",
75 "https://dirkjanm.io/phishing-for-microsoft-entra-primary-refresh-tokens/",
76]
77risk_score = 47
78rule_id = "fd9d2933-f0f9-4aac-810c-a31f6a4a7890"
79setup = """#### Azure AD Graph Activity Logs
80Requires Azure AD Graph Activity Logs ingested into `logs-azure.aadgraphactivitylogs-*` via the Elastic Azure
81integration. Enable the `AzureADGraphActivityLogs` diagnostic-settings category on Entra ID.
82"""
83severity = "medium"
84tags = [
85 "Domain: Cloud",
86 "Data Source: Azure",
87 "Data Source: Azure AD Graph",
88 "Data Source: Azure AD Graph Activity Logs",
89 "Use Case: Threat Detection",
90 "Tactic: Defense Evasion",
91 "Tactic: Discovery",
92 "Resources: Investigation Guide",
93]
94timestamp_override = "event.ingested"
95type = "new_terms"
96
97query = '''
98data_stream.dataset:"azure.aadgraphactivitylogs" and
99 azure.aadgraphactivitylogs.properties.actor_type : "User" and
100 azure.aadgraphactivitylogs.properties.app_id: (* and not (
101 "74658136-14ec-4630-ad9b-26e160ff0fc6" or
102 "bb8f18b0-9c38-48c9-a847-e1ef3af0602d" or
103 "00000006-0000-0ff1-ce00-000000000000" or
104 "18ed3507-a475-4ccb-b669-d66bc9f2a36e")
105 ) and user.id:*
106'''
107
108[[rule.threat]]
109framework = "MITRE ATT&CK"
110[[rule.threat.technique]]
111id = "T1087"
112name = "Account Discovery"
113reference = "https://attack.mitre.org/techniques/T1087/"
114[[rule.threat.technique.subtechnique]]
115id = "T1087.004"
116name = "Cloud Account"
117reference = "https://attack.mitre.org/techniques/T1087/004/"
118
119[rule.threat.tactic]
120id = "TA0007"
121name = "Discovery"
122reference = "https://attack.mitre.org/tactics/TA0007/"
123
124[[rule.threat]]
125framework = "MITRE ATT&CK"
126[[rule.threat.technique]]
127id = "T1550"
128name = "Use Alternate Authentication Material"
129reference = "https://attack.mitre.org/techniques/T1550/"
130[[rule.threat.technique.subtechnique]]
131id = "T1550.001"
132name = "Application Access Token"
133reference = "https://attack.mitre.org/techniques/T1550/001/"
134
135[rule.threat.tactic]
136id = "TA0005"
137name = "Defense Evasion"
138reference = "https://attack.mitre.org/tactics/TA0005/"
139
140
141[rule.investigation_fields]
142field_names = [
143 "user.id",
144 "source.ip",
145 "source.as.organization.name",
146 "user_agent.original",
147 "azure.aadgraphactivitylogs.properties.app_id",
148 "azure.aadgraphactivitylogs.properties.api_version",
149 "url.path",
150 "azure.tenant_id",
151]
152
153[rule.new_terms]
154field = "new_terms_fields"
155value = ["azure.aadgraphactivitylogs.properties.app_id", "user.id"]
156[[rule.new_terms.history_window_start]]
157field = "history_window_start"
158value = "now-7d"
Triage and analysis
Investigating Azure AD Graph Access with Unusual Client and User
A (client, user) pair appearing on AAD Graph for the first time in 14 days is a high-signal indicator. Legacy AAD Graph traffic is dominated by a small set of recognised first-party callers per user; new combinations suggest one of:
- A FOCI refresh-token swap landed under a new client identity.
- The user newly consented to (or was phished into consenting to) an OAuth application.
- An adversary is using stolen tokens / credentials under a client identity the user does not normally use.
Possible investigation steps
- Identify the calling client and confirm whether the app is sanctioned.
azure.aadgraphactivitylogs.properties.app_id. Pivot to Azure portal → Enterprise Applications to check whether it is first-party, sanctioned third-party, or unfamiliar.
- Identify the user and check the surrounding auth context.
user.id, then pivot to sign-in logs (logs-azure.signinlogs-*) for the same user around the same time. Look for unusual sign-in geography, MFA bypass, or risky session signals.
- Review source posture.
user_agent.original,source.ip,source.as.organization.name. Residential / VPS / anonymising-network egress raises priority.
- Review what was queried.
url.path. Bulk recon or User-collection access via internal API versions raises triage priority.
- Check tenant-wide blast radius for the client.
- Is the same client ID hitting AAD Graph for many other users? That pattern points to large-scale consent abuse rather than a single account compromise.
- Confirm the activity is not attributable to authorized testing (red team engagement, penetration test, internal tooling validation) before treating as malicious.
Response and remediation
- Revoke refresh tokens and active sessions for the calling 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}.
- If the client is not a sanctioned application, revoke its OAuth consent.
GET /v1.0/oauth2PermissionGrants?$filter=clientId eq '{servicePrincipalId}', thenDELETE /v1.0/oauth2PermissionGrants/{grantId}.
- 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.
References
Related rules
- Azure AD Graph High 4xx Error Ratio from User
- Azure AD Graph Access with Suspicious User-Agent
- Entra ID OAuth Device Code Sign-in to Azure AD Graph Enumeration
- Azure AD Graph Potential Enumeration (ROADrecon)
- Azure VM Serial Console Connection with Unusual User and ASN