Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
[metadata]
creation_date = "2026/05/22"
integration = ["azure"]
maturity = "production"
updated_date = "2026/05/22"

[rule]
author = ["Elastic"]
description = """
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
the previous 14 days. 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. This is a new-terms style detection that fires on first
observation of a (client, user) pair against AAD Graph.
"""
false_positives = [
"""
First-time use of a legitimate first-party or sanctioned client by the user (newly installed app, first sign-in to a
workload, fresh PowerShell module install). Validate by `azure.aadgraphactivitylogs.properties.app_id` and the
user's history.
""",
" Authorized red team or audit activity. Add exceptions on the calling user after review.\n ",
]
from = "now-9m"
index = ["logs-azure.aadgraphactivitylogs-*"]
language = "kuery"
license = "Elastic License v2"
name = "Azure AD Graph Access with Unusual Client and User"
note = """## Triage and analysis

### Investigating Entra ID AAD Graph Access by 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}'`, then `DELETE /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}/registeredDevices` and `GET /v1.0/users/{id}/ownedDevices`, then `DELETE /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 = [
"https://github.com/secureworks/family-of-client-ids-research",
"https://learn.microsoft.com/en-us/graph/migrate-azure-ad-graph-overview",
"https://dirkjanm.io/phishing-for-microsoft-entra-primary-refresh-tokens/",
]
risk_score = 47
rule_id = "fd9d2933-f0f9-4aac-810c-a31f6a4a7890"
setup = """#### Azure AD Graph Activity Logs
Requires Azure AD Graph Activity Logs ingested into `logs-azure.aadgraphactivitylogs-*` via the Elastic Azure
integration. Enable the `AzureADGraphActivityLogs` diagnostic-settings category on Entra ID.
"""
severity = "medium"
tags = [
"Domain: Cloud",
"Data Source: Azure",
"Data Source: Azure AD Graph",
"Data Source: Azure AD Graph Activity Logs",
"Use Case: Threat Detection",
"Tactic: Defense Evasion",
"Tactic: Discovery",
"Resources: Investigation Guide",
]
timestamp_override = "event.ingested"
type = "new_terms"

query = '''
data_stream.dataset:"azure.aadgraphactivitylogs" and
azure.aadgraphactivitylogs.properties.actor_type : "User" and
azure.aadgraphactivitylogs.properties.app_id: (* and not (
"74658136-14ec-4630-ad9b-26e160ff0fc6" or
"bb8f18b0-9c38-48c9-a847-e1ef3af0602d" or
"00000006-0000-0ff1-ce00-000000000000" or
"18ed3507-a475-4ccb-b669-d66bc9f2a36e")
) and user.id:*
'''


[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1550"
name = "Use Alternate Authentication Material"
reference = "https://attack.mitre.org/techniques/T1550/"
[[rule.threat.technique.subtechnique]]
id = "T1550.001"
name = "Application Access Token"
reference = "https://attack.mitre.org/techniques/T1550/001/"



[rule.threat.tactic]
id = "TA0005"
name = "Defense Evasion"
reference = "https://attack.mitre.org/tactics/TA0005/"
[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1087"
name = "Account Discovery"
reference = "https://attack.mitre.org/techniques/T1087/"
[[rule.threat.technique.subtechnique]]
id = "T1087.004"
name = "Cloud Account"
reference = "https://attack.mitre.org/techniques/T1087/004/"



[rule.threat.tactic]
id = "TA0007"
name = "Discovery"
reference = "https://attack.mitre.org/tactics/TA0007/"

[rule.investigation_fields]
field_names = [
"user.id",
"source.ip",
"source.as.organization.name",
"user_agent.original",
"azure.aadgraphactivitylogs.properties.app_id",
"azure.aadgraphactivitylogs.properties.api_version",
"url.path",
"azure.tenant_id",
]

[rule.new_terms]
field = "new_terms_fields"
value = ["azure.aadgraphactivitylogs.properties.app_id", "user.id"]
[[rule.new_terms.history_window_start]]
field = "history_window_start"
value = "now-7d"


Loading