Azure AD Graph Access with Suspicious User-Agent
editAzure AD Graph Access with Suspicious User-Agent
editIdentifies Azure AD Graph (graph.windows.net) requests originating from user-agent strings associated with offensive tooling, scripting libraries, or generic HTTP clients. First-party Microsoft components calling AAD Graph identify with specific user agents such as "Microsoft Azure Graph Client Library", "Microsoft ADO.NET Data Services", or "Microsoft.OData.Client". Anything outside that recognised set is either a developer prototyping against the legacy API or an enumeration tool walking the directory.
Rule type: esql
Rule indices: None
Severity: medium
Risk score: 47
Runs every: 9m
Searches indices from: now-10m (Date Math format, see also Additional look-back time)
Maximum alerts per execution: 100
References:
Tags:
- Domain: Cloud
- Data Source: Azure
- Data Source: Azure AD Graph
- Data Source: Azure AD Graph Activity Logs
- Use Case: Threat Detection
- Tactic: Discovery
- Resources: Investigation Guide
Version: 2
Rule authors:
- Elastic
Rule license: Elastic License v2
Investigation guide
editTriage and analysis
Investigating Azure AD Graph Access with Suspicious User-Agent
Azure AD Graph (graph.windows.net) is the legacy directory REST API that Microsoft has been retiring for years.
Legitimate first-party traffic against it is dominated by a small set of recognisable user agents (Microsoft.OData.Client,
Microsoft Azure Graph Client Library, Microsoft ADO.NET Data Services, the Azure portal with a Chrome user agent,
and an empty-UA tail from first-party AppIds). Traffic identifying as Python, aiohttp, curl, Go-http-client, or any
of the *hound enumeration families is almost always either a developer prototype or adversary tooling. This rule
flags any such request even at a single event, because tooling samples for AAD Graph are inherently low-volume in
normal tenants.
Possible investigation steps
- Confirm the matching user agent.
-
user_agent.original(e.g.,aiohttp,AADInternals,curl,bav2ropc). - Identify the caller and the calling client.
-
user.idfor the caller,azure.aadgraphactivitylogs.properties.app_idfor the OAuth client. - Review which directory object types were touched.
-
url.path(e.g.,/users,/policies,/servicePrincipals). - Check the success / failure pattern.
-
http.response.status_code. Many 4xx responses suggest permission probing. - Cross-reference with the API version.
-
azure.aadgraphactivitylogs.properties.api_version. A non-Microsoft UA combined with1.6-internalor1.61-internalis a stronger signal of offensive tooling. -
Pivot to sign-in logs (
logs-azure.signinlogs-*) for the same user / source IP to understand how the token was obtained. - 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}. - 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 AAD Graph audience for the affected user population.
Setup
editAzure AD Graph Activity Logs
Requires Azure AD Graph Activity Logs ingested into logs-azure.aadgraphactivitylogs-* via the Elastic Azure
integration (Azure Event Hub). Enable the AzureADGraphActivityLogs diagnostic-settings category on Entra ID.
Rule query
editfrom logs-azure.aadgraphactivitylogs-* metadata _id, _version, _index
| where data_stream.dataset == "azure.aadgraphactivitylogs"
and azure.aadgraphactivitylogs.properties.actor_type == "User"
and user_agent.original is not null
| eval Esql.ua_lower = to_lower(user_agent.original)
| where Esql.ua_lower like "*fasthttp*"
or Esql.ua_lower like "*aiohttp*"
or Esql.ua_lower like "*hound*"
or Esql.ua_lower like "*aadinternals*"
or Esql.ua_lower like "*go-http-client*"
or Esql.ua_lower like "python*"
or Esql.ua_lower like "*curl/*"
or Esql.ua_lower like "*okhttp*"
or Esql.ua_lower like "*axios*"
or Esql.ua_lower like "*node-fetch*"
or Esql.ua_lower like "*go-resty*"
or Esql.ua_lower like "*bav2ropc*"
or Esql.ua_lower like "*undici*"
| keep
_id,
_version,
_index,
@timestamp,
user.id,
source.ip,
source.as.organization.name,
user_agent.original,
azure.aadgraphactivitylogs.properties.app_id,
azure.aadgraphactivitylogs.properties.api_version,
url.path,
http.response.status_code,
azure.tenant_id,
Esql.ua_lower
Framework: MITRE ATT&CKTM
-
Tactic:
- Name: Discovery
- ID: TA0007
- Reference URL: https://attack.mitre.org/tactics/TA0007/
-
Technique:
- Name: Permission Groups Discovery
- ID: T1069
- Reference URL: https://attack.mitre.org/techniques/T1069/
-
Sub-technique:
- Name: Cloud Groups
- ID: T1069.003
- Reference URL: https://attack.mitre.org/techniques/T1069/003/
-
Technique:
- Name: Account Discovery
- ID: T1087
- Reference URL: https://attack.mitre.org/techniques/T1087/
-
Sub-technique:
- Name: Cloud Account
- ID: T1087.004
- Reference URL: https://attack.mitre.org/techniques/T1087/004/
-
Technique:
- Name: Cloud Service Discovery
- ID: T1526
- Reference URL: https://attack.mitre.org/techniques/T1526/