Microsoft Graph Application Directory Reconnaissance with Denied Requests
Identifies a Microsoft Graph application querying multiple directory collections and encountering access denials. Adversaries with compromised application credentials may enumerate users, groups, applications, service principals, directory roles, role assignments, and OAuth grants to identify privileged identities and opportunities for further access.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2026/11/03"
3integration = ["azure"]
4maturity = "production"
5updated_date = "2026/11/04"
6
7[rule]
8author = ["Elastic"]
9description = """
10Identifies a Microsoft Graph application querying multiple directory collections and encountering access denials.
11Adversaries with compromised application credentials may enumerate users, groups, applications, service principals,
12directory roles, role assignments, and OAuth grants to identify privileged identities and opportunities for further
13access.
14"""
15false_positives = [
16 """
17 Application onboarding, directory synchronization, inventory tools, and permission-validation scripts may query
18 multiple directory collections and encounter expected access denials.
19 """,
20]
21from = "now-30m"
22interval = "5m"
23language = "esql"
24license = "Elastic License v2"
25name = "Microsoft Graph Application Directory Reconnaissance with Denied Requests"
26note = """## Triage and analysis
27
28### Investigating Microsoft Graph Application Directory Reconnaissance with Denied Requests
29
30Microsoft Graph exposes directory information that can help an adversary identify accounts, privileged identities, and applications to target. This rule identifies app-only requests to at least three common directory discovery collections (applications, service principals, users, groups, directory roles, role assignments, and OAuth permission grants) within a 15-minute window, with at least one HTTP 403 response. The denial requirement reduces noise from authorized inventory applications that succeed across the collections they are granted. A denied request shows attempted discovery, not successful retrieval of that collection.
31
32### Possible investigation steps
33
34- Identify the calling principal using `azure.tenant_id` and `azure.graphactivitylogs.properties.service_principal_id`. Review `Esql.app_ids` and resolve the application owner and expected directory permissions.
35- Review `Esql.time_window`, `Esql.request_count`, `Esql.category_count`, and `Esql.denied_count` to establish the scope of the discovery. Use `Esql.paths`, `Esql.status_codes`, and `Esql.source_ips` to review the targeted collections, outcomes, and sources.
36- Examine the original Graph events to distinguish successfully accessed collections from denied requests. Determine whether the queried collections are needed for the application's documented purpose.
37- Correlate the principal with service-principal sign-ins, recent application credential additions, and changes to application permissions. Where available, compare `azure.signinlogs.properties.service_principal_credential_key_id` with the credential added in audit records.
38- Look for related role-assignment attempts, successful privilege changes, secret access, and access to other services. Distinguish the calling application from applications returned or targeted by its requests.
39- Confirm whether an inventory, directory synchronization, onboarding, or permission-validation workflow explains the activity.
40
41### False positive analysis
42
43- Directory inventory and synchronization applications may access several collections and encounter expected permission denials. Validate the activity with the application owner and compare it with normal sources and access patterns.
44- Onboarding and permission-validation scripts may generate similar requests. Scope any justified exception to the tenant, principal, and expected workflow, and reassess it when permissions or ownership change.
45
46### Response and remediation
47
48- If compromise is confirmed, restrict the affected principal and remove the specific compromised credential and unnecessary permissions according to the incident response process.
49- Review and remediate unauthorized privilege changes or downstream resource access associated with the principal.
50- Verify that subsequent access is denied after containment; credential removal alone does not establish that previously issued access tokens are unusable.
51- Preserve the original Graph activity, audit, and sign-in events for investigation.
52"""
53references = [
54 "https://www.microsoft.com/en-us/security/blog/2026/10/21/storm-0940-uses-artex-agentic-ai-connectwise-supply-chain-compromise/",
55 "https://learn.microsoft.com/en-us/graph/microsoft-graph-activity-logs-overview",
56]
57risk_score = 47
58rule_id = "ed66918f-5fb0-4cb2-a684-47e5d72e0a50"
59setup = """#### Microsoft Graph Activity Logs
60
61Enable Microsoft Graph Activity Logs and ingest them through the Azure integration into
62`logs-azure.graphactivitylogs-*`. Retain the tenant and service-principal IDs, HTTP method, URL path, and response
63status. The `azure.graphactivitylogs.properties.c_idtyp` field must be populated with the token actor type;
64this rule selects `app` and does not infer app-only authentication from an application ID alone.
65
66The rule evaluates a 30-minute ingestion lookback every five minutes and groups requests into fixed 15-minute
67event-time windows. Requests split across window boundaries or ingested more than 30 minutes apart may not be
68combined. Review delivery latency and repeated aggregate alerts when tuning the schedule. The rule covers
69the direct v1.0 collection paths in the query; beta endpoints and other request paths are outside its scope.
70"""
71severity = "medium"
72tags = [
73 "Domain: Cloud",
74 "Domain: Identity",
75 "Data Source: Azure",
76 "Data Source: Microsoft Graph Activity Logs",
77 "Use Case: Threat Detection",
78 "Tactic: Discovery",
79 "Resources: Investigation Guide",
80 "Rule Type: ES|QL",
81 "Platform: Microsoft Graph",
82 "Platform: Azure",
83]
84timestamp_override = "event.ingested"
85type = "esql"
86
87query = '''
88from logs-azure.graphactivitylogs-* metadata _id, _index
89| where data_stream.dataset == "azure.graphactivitylogs"
90 and azure.graphactivitylogs.properties.c_idtyp == "app"
91 and azure.tenant_id is not null and azure.tenant_id != ""
92 and azure.graphactivitylogs.properties.service_principal_id is not null
93 and azure.graphactivitylogs.properties.service_principal_id != ""
94 and http.request.method == "GET"
95 and http.response.status_code in (200, 403)
96 and url.path in (
97 "/v1.0/applications",
98 "/v1.0/servicePrincipals",
99 "/v1.0/users",
100 "/v1.0/groups",
101 "/v1.0/directoryRoles",
102 "/v1.0/roleManagement/directory/roleAssignments",
103 "/v1.0/oauth2PermissionGrants"
104)
105| eval Esql.time_window = date_trunc(15 minutes, @timestamp)
106| stats
107 Esql.request_count = count(*),
108 Esql.category_count = count_distinct(url.path),
109 Esql.denied_count = sum(case(http.response.status_code == 403, 1, 0)),
110 Esql.paths = values(url.path),
111 Esql.status_codes = values(http.response.status_code),
112 Esql.app_ids = values(azure.graphactivitylogs.properties.app_id),
113 Esql.source_ips = values(source.ip),
114 Esql.first_seen = min(@timestamp),
115 Esql.last_seen = max(@timestamp)
116 by Esql.time_window, azure.tenant_id, azure.graphactivitylogs.properties.service_principal_id
117| where Esql.category_count >= 3 and Esql.denied_count >= 1
118| keep azure.tenant_id, azure.graphactivitylogs.properties.service_principal_id, Esql.*
119'''
120
121[rule.alert_suppression]
122group_by = [
123 "azure.tenant_id",
124 "azure.graphactivitylogs.properties.service_principal_id",
125]
126missing_fields_strategy = "suppress"
127
128[rule.alert_suppression.duration]
129unit = "m"
130value = 30
131
132[[rule.threat]]
133framework = "MITRE ATT&CK"
134[[rule.threat.technique]]
135id = "T1069"
136name = "Permission Groups Discovery"
137reference = "https://attack.mitre.org/techniques/T1069/"
138[[rule.threat.technique.subtechnique]]
139id = "T1069.003"
140name = "Cloud Groups"
141reference = "https://attack.mitre.org/techniques/T1069/003/"
142
143
144[[rule.threat.technique]]
145id = "T1087"
146name = "Account Discovery"
147reference = "https://attack.mitre.org/techniques/T1087/"
148[[rule.threat.technique.subtechnique]]
149id = "T1087.004"
150name = "Cloud Account"
151reference = "https://attack.mitre.org/techniques/T1087/004/"
152
153
154
155[rule.threat.tactic]
156id = "TA0007"
157name = "Discovery"
158reference = "https://attack.mitre.org/tactics/TA0007/"
159
160[rule.investigation_fields]
161field_names = [
162 "azure.tenant_id",
163 "azure.graphactivitylogs.properties.service_principal_id",
164 "Esql.paths",
165 "Esql.status_codes",
166 "Esql.source_ips",
167 "Esql.time_window",
168]
Triage and analysis
Investigating Microsoft Graph Application Directory Reconnaissance with Denied Requests
Microsoft Graph exposes directory information that can help an adversary identify accounts, privileged identities, and applications to target. This rule identifies app-only requests to at least three common directory discovery collections (applications, service principals, users, groups, directory roles, role assignments, and OAuth permission grants) within a 15-minute window, with at least one HTTP 403 response. The denial requirement reduces noise from authorized inventory applications that succeed across the collections they are granted. A denied request shows attempted discovery, not successful retrieval of that collection.
Possible investigation steps
- Identify the calling principal using
azure.tenant_idandazure.graphactivitylogs.properties.service_principal_id. ReviewEsql.app_idsand resolve the application owner and expected directory permissions. - Review
Esql.time_window,Esql.request_count,Esql.category_count, andEsql.denied_countto establish the scope of the discovery. UseEsql.paths,Esql.status_codes, andEsql.source_ipsto review the targeted collections, outcomes, and sources. - Examine the original Graph events to distinguish successfully accessed collections from denied requests. Determine whether the queried collections are needed for the application's documented purpose.
- Correlate the principal with service-principal sign-ins, recent application credential additions, and changes to application permissions. Where available, compare
azure.signinlogs.properties.service_principal_credential_key_idwith the credential added in audit records. - Look for related role-assignment attempts, successful privilege changes, secret access, and access to other services. Distinguish the calling application from applications returned or targeted by its requests.
- Confirm whether an inventory, directory synchronization, onboarding, or permission-validation workflow explains the activity.
False positive analysis
- Directory inventory and synchronization applications may access several collections and encounter expected permission denials. Validate the activity with the application owner and compare it with normal sources and access patterns.
- Onboarding and permission-validation scripts may generate similar requests. Scope any justified exception to the tenant, principal, and expected workflow, and reassess it when permissions or ownership change.
Response and remediation
- If compromise is confirmed, restrict the affected principal and remove the specific compromised credential and unnecessary permissions according to the incident response process.
- Review and remediate unauthorized privilege changes or downstream resource access associated with the principal.
- Verify that subsequent access is denied after containment; credential removal alone does not establish that previously issued access tokens are unusable.
- Preserve the original Graph activity, audit, and sign-in events for investigation.
References
Related rules
- Microsoft Graph Multi-Category Reconnaissance Burst
- Azure Key Vault Multi-Vault Secret Retrieval Burst by Application
- Entra ID Managed Device Client Sign-In to Graph Enumeration
- Azure AKS Multi-Resource Setup Discovery
- Azure AD Graph Potential Enumeration (ROADrecon)