Skip to content

Commit dd2efcf

Browse files
Merge branch 'main' into rt_3
2 parents 1236b94 + 909ff9c commit dd2efcf

9 files changed

+459
-6
lines changed

detection_rules/etc/non-ecs-schema.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,8 @@
184184
},
185185
"logs-azure.graphactivitylogs-*": {
186186
"azure.graphactivitylogs.properties.c_idtyp": "keyword",
187-
"azure.graphactivitylogs.properties.user_principal_object_id": "keyword"
187+
"azure.graphactivitylogs.properties.user_principal_object_id": "keyword",
188+
"azure.graphactivitylogs.properties.requestUri": "keyword"
188189
},
189190
"logs-o365.audit-*": {
190191
"o365.audit.ExtendedProperties.ResultStatusDetail": "keyword"
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Microsoft Entra Infrequent Suspicious OData Client Requests
2+
3+
---
4+
5+
## Metadata
6+
7+
- **Author:** Elastic
8+
- **Description:** Identifies infrequent OData client requests in Microsoft Entra ID. This behavior may indicate an adversary using a custom or Azure-managed app ID to authenticate on behalf of a user. This is a rare event and may indicate an attempt to bypass conditional access policies (CAP) and multi-factor authentication (MFA) requirements. The app ID specified may not be commonly used by the user based on their historical sign-in activity. The OData client is used in ROADTools, a toolset leveraged by threat actors to automate OAuth and OIDC workflows in Microsoft Entra ID following phishing or token theft.
9+
10+
- **UUID:** `0d3d2254-2b4a-11f0-a019-f661ea17fbcc`
11+
- **Integration:** [azure](https://docs.elastic.co/integrations/azure)
12+
- **Language:** `[ES|QL]`
13+
- **Source File:** [Microsoft Entra Infrequent Suspicious OData Client Requests](../queries/entra_suspicious_odata_client_requests.toml)
14+
15+
## Query
16+
17+
```sql
18+
FROM logs-azure.auditlogs* METADATA _id, _index
19+
20+
// Only Microsoft Entra ID audit logs
21+
| WHERE event.dataset == "azure.auditlogs"
22+
23+
// Identify logs with the known suspicious OData user agent
24+
AND azure.auditlogs.properties.additional_details.value LIKE "Microsoft.OData.Client/*"
25+
AND azure.auditlogs.identity != "Device Registration Service"
26+
27+
// Extract time window for pattern analysis
28+
| EVAL time_window = DATE_TRUNC(1d, @timestamp)
29+
30+
// Normalize actor: prefer user UPN if available, else fallback to app name
31+
| EVAL actor = COALESCE(
32+
azure.auditlogs.properties.initiated_by.user.userPrincipalName,
33+
azure.auditlogs.properties.initiated_by.app.displayName,
34+
"unknown"
35+
)
36+
37+
// Keep core fields
38+
| KEEP @timestamp, actor, source.ip, azure.auditlogs.operation_name, azure.auditlogs.properties.activity_display_name, azure.auditlogs.identity, azure.auditlogs.properties.tenantId, azure.auditlogs.properties.initiated_by.app.servicePrincipalId, azure.auditlogs.properties.category, azure.auditlogs.properties.additional_details.value, time_window
39+
40+
// Group by actor per day
41+
| STATS
42+
count = COUNT(),
43+
unique_ips = COUNT_DISTINCT(source.ip),
44+
operations = VALUES(azure.auditlogs.operation_name),
45+
identities = VALUES(azure.auditlogs.identity),
46+
ips = VALUES(source.ip),
47+
categories = VALUES(azure.auditlogs.properties.category),
48+
clients = VALUES(azure.auditlogs.properties.additional_details.value)
49+
BY actor, time_window
50+
51+
// Optional: prioritize less frequent actors
52+
| SORT count ASC
53+
```
54+
55+
## Notes
56+
57+
- Review `azure.auditlogs.properties.additional_details.value` for `Microsoft.OData.Client/*` User-Agent strings. This is uncommon for legitimate first-party Microsoft applications and may indicate use of ROADTools or custom automation to register a device and obtain a PRT.
58+
- Check `azure.auditlogs.properties.initiated_by` for both `user` and `app` fields. The presence of both may suggest an OAuth on-behalf-of (OBO) flow, where the app is acting with delegated permissions from a phished user token.
59+
- Review `azure.auditlogs.properties.activity_display_name` and `operation_name` for device registration operations like `Add device` or `Add registered owner to device`. When combined with suspicious user-agents, this may indicate unauthorized device registration.
60+
- Review `azure.auditlogs.identity` for operations performed by `Device Registration Service`. This service name is commonly associated with device join flows that, if abused, may enable persistence via PRT acquisition.
61+
- Correlate with `azure.signinlogs` for sign-ins using the same `correlation_id` or `userPrincipalName`. Look for signs of previous OAuth token issuance or multi-geo IP behavior surrounding the device registration.
62+
- Investigate whether the device registered (under `azure.auditlogs.properties.target_resources`) corresponds to known or authorized endpoints. Devices with names like `DESKTOP-ATTACKER1` or unexpected OS versions may indicate rogue joins.
63+
- The source IP is likely to be Microsoft-managed infrastructure as requests are proxied through Azure services. Pivoting into which user principal the request is targeting and events leading up to the request may provide additional context.
64+
65+
## MITRE ATT&CK Techniques
66+
67+
- [T1078.004](https://attack.mitre.org/techniques/T1078/004)
68+
- [T1550.001](https://attack.mitre.org/techniques/T1550/001)
69+
- [T1098.005](https://attack.mitre.org/techniques/T1098/005)
70+
- [T1071.001](https://attack.mitre.org/techniques/T1071/001)
71+
- [T1556.006](https://attack.mitre.org/techniques/T1556/006)
72+
73+
## References
74+
75+
- https://www.volexity.com/blog/2025/04/22/phishing-for-codes-russian-threat-actors-target-microsoft-365-oauth-workflows/
76+
77+
## License
78+
79+
- `Elastic License v2`
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
[hunt]
2+
author = "Elastic"
3+
description = """
4+
Identifies infrequent OData client requests in Microsoft Entra ID. This behavior may indicate an adversary using a custom or Azure-managed app ID to authenticate on behalf of a user. This is a rare event and may indicate an attempt to bypass conditional access policies (CAP) and multi-factor authentication (MFA) requirements. The app ID specified may not be commonly used by the user based on their historical sign-in activity. The OData client is used in ROADTools, a toolset leveraged by threat actors to automate OAuth and OIDC workflows in Microsoft Entra ID following phishing or token theft.
5+
"""
6+
integration = ["azure"]
7+
uuid = "0d3d2254-2b4a-11f0-a019-f661ea17fbcc"
8+
name = "Microsoft Entra Infrequent Suspicious OData Client Requests"
9+
language = ["ES|QL"]
10+
license = "Elastic License v2"
11+
notes = [
12+
"Review `azure.auditlogs.properties.additional_details.value` for `Microsoft.OData.Client/*` User-Agent strings. This is uncommon for legitimate first-party Microsoft applications and may indicate use of ROADTools or custom automation to register a device and obtain a PRT.",
13+
"Check `azure.auditlogs.properties.initiated_by` for both `user` and `app` fields. The presence of both may suggest an OAuth on-behalf-of (OBO) flow, where the app is acting with delegated permissions from a phished user token.",
14+
"Review `azure.auditlogs.properties.activity_display_name` and `operation_name` for device registration operations like `Add device` or `Add registered owner to device`. When combined with suspicious user-agents, this may indicate unauthorized device registration.",
15+
"Review `azure.auditlogs.identity` for operations performed by `Device Registration Service`. This service name is commonly associated with device join flows that, if abused, may enable persistence via PRT acquisition.",
16+
"Correlate with `azure.signinlogs` for sign-ins using the same `correlation_id` or `userPrincipalName`. Look for signs of previous OAuth token issuance or multi-geo IP behavior surrounding the device registration.",
17+
"Investigate whether the device registered (under `azure.auditlogs.properties.target_resources`) corresponds to known or authorized endpoints. Devices with names like `DESKTOP-ATTACKER1` or unexpected OS versions may indicate rogue joins.",
18+
"The source IP is likely to be Microsoft-managed infrastructure as requests are proxied through Azure services. Pivoting into which user principal the request is targeting and events leading up to the request may provide additional context."
19+
]
20+
mitre = [
21+
"T1078.004",
22+
"T1550.001",
23+
"T1098.005",
24+
"T1071.001",
25+
"T1556.006",
26+
]
27+
references = ["https://www.volexity.com/blog/2025/04/22/phishing-for-codes-russian-threat-actors-target-microsoft-365-oauth-workflows/"]
28+
query = [
29+
'''
30+
FROM logs-azure.auditlogs* METADATA _id, _index
31+
32+
// Only Microsoft Entra ID audit logs
33+
| WHERE event.dataset == "azure.auditlogs"
34+
35+
// Identify logs with the known suspicious OData user agent
36+
AND azure.auditlogs.properties.additional_details.value LIKE "Microsoft.OData.Client/*"
37+
AND azure.auditlogs.identity != "Device Registration Service"
38+
39+
// Extract time window for pattern analysis
40+
| EVAL time_window = DATE_TRUNC(1d, @timestamp)
41+
42+
// Normalize actor: prefer user UPN if available, else fallback to app name
43+
| EVAL actor = COALESCE(
44+
azure.auditlogs.properties.initiated_by.user.userPrincipalName,
45+
azure.auditlogs.properties.initiated_by.app.displayName,
46+
"unknown"
47+
)
48+
49+
// Keep core fields
50+
| KEEP @timestamp, actor, source.ip, azure.auditlogs.operation_name, azure.auditlogs.properties.activity_display_name, azure.auditlogs.identity, azure.auditlogs.properties.tenantId, azure.auditlogs.properties.initiated_by.app.servicePrincipalId, azure.auditlogs.properties.category, azure.auditlogs.properties.additional_details.value, time_window
51+
52+
// Group by actor per day
53+
| STATS
54+
count = COUNT(),
55+
unique_ips = COUNT_DISTINCT(source.ip),
56+
operations = VALUES(azure.auditlogs.operation_name),
57+
identities = VALUES(azure.auditlogs.identity),
58+
ips = VALUES(source.ip),
59+
categories = VALUES(azure.auditlogs.properties.category),
60+
clients = VALUES(azure.auditlogs.properties.additional_details.value)
61+
BY actor, time_window
62+
63+
// Optional: prioritize less frequent actors
64+
| SORT count ASC
65+
'''
66+
]

hunting/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Here are the queries currently available:
3737
- [Azure Entra Unusual Client App Authentication Requests on Behalf of Principal Users](./azure/docs/entra_unusual_client_app_auth_request_on_behalf_of_user.md) (ES|QL)
3838
- [Azure Entra Unusual Failed Authentication Attempts Behind Rare User Agents](./azure/docs/entra_authentication_attempts_behind_rare_user_agents.md) (ES|QL)
3939
- [Microsoft Entra ID Credentials Added to Rare Service Principal](./azure/docs/entra_service_principal_credentials_added_to_rare_app.md) (ES|QL)
40+
- [Microsoft Entra Infrequent Suspicious OData Client Requests](./azure/docs/entra_suspicious_odata_client_requests.md) (ES|QL)
4041

4142

4243
## linux

hunting/index.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,3 +757,12 @@ azure:
757757
path: ./azure/queries/entra_service_principal_credentials_added_to_rare_app.toml
758758
mitre:
759759
- T1098.001
760+
0d3d2254-2b4a-11f0-a019-f661ea17fbcc:
761+
name: Microsoft Entra Infrequent Suspicious OData Client Requests
762+
path: ./azure/queries/entra_suspicious_odata_client_requests.toml
763+
mitre:
764+
- T1078.004
765+
- T1550.001
766+
- T1098.005
767+
- T1071.001
768+
- T1556.006

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "detection_rules"
3-
version = "1.2.4"
3+
version = "1.2.5"
44
description = "Detection Rules is the home for rules used by Elastic Security. This repository is used for the development, maintenance, testing, validation, and release of rules for Elastic Security’s Detection Engine."
55
readme = "README.md"
66
requires-python = ">=3.12"
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
[metadata]
2+
creation_date = "2025/05/06"
3+
integration = ["azure"]
4+
maturity = "production"
5+
updated_date = "2025/05/06"
6+
7+
[rule]
8+
author = ["Elastic"]
9+
description = """
10+
Identifies access to email resources via Microsoft Graph API using an first-party application on behalf of a user
11+
principal. This behavior may indicate an adversary using a phished OAuth refresh token or a Primary Refresh Token (PRT)
12+
to access email resources. The pattern includes requests to Microsoft Graph API endpoints related to email, such as
13+
/me/mailFolders/inbox/messages or /users/{user_id}/messages, using a public client application ID and a user principal
14+
object ID. This is a New Terms rule that only signals if the application ID and user principal object ID have not been
15+
seen doing this activity in the last 14 days.
16+
"""
17+
from = "now-9m"
18+
index = ["filebeat-*", "logs-azure.graphactivitylogs-*"]
19+
language = "kuery"
20+
license = "Elastic License v2"
21+
name = "Suspicious Email Access by First-Party Application via Microsoft Graph"
22+
note = """## Triage and analysis
23+
24+
### Investigating Suspicious Email Access by First-Party Application via Microsoft Graph
25+
26+
This rule detects instances where a previously unseen or rare Microsoft Graph application client ID accesses email-related APIs, such as `/me/messages`, `/sendMail`, or `/mailFolders/inbox/messages`. These accesses are performed via delegated user credentials using common OAuth scopes like `Mail.Read`, `Mail.ReadWrite`, `Mail.Send`, or `email`. This activity may indicate unauthorized use of a newly consented or compromised application to read or exfiltrate mail content. This is a New Terms rule that only signals if the application ID (`azure.graphactivitylogs.properties.app_id`) and user principal object ID (`azure.graphactivitylogs.properties.user_principal_object_id`) have not been seen doing this activity in the last 14 days.
27+
28+
### Possible Investigation Steps:
29+
30+
- `azure.graphactivitylogs.properties.app_id`: Investigate the application ID involved. Is it known and sanctioned in your tenant? Pivot to Azure Portal → Enterprise Applications → Search by App ID to determine app details, publisher, and consent status.
31+
- `azure.graphactivitylogs.properties.scopes`: Review the scopes requested by the application. Email-related scopes such as `Mail.ReadWrite` and `Mail.Send` are especially sensitive and suggest the app is interacting with mail content.
32+
- `url.path` / `azure.graphactivitylogs.properties.requestUri`: Determine exactly which mail-related APIs were accessed (e.g., reading inbox, sending messages, enumerating folders).
33+
- `user.id`: Identify the user whose credentials were used. Determine if the user recently consented to a new app, clicked a phishing link, or reported suspicious activity.
34+
- `user_agent.original`: Check for suspicious automation tools (e.g., `python-requests`, `curl`, non-browser agents), which may suggest scripted access.
35+
- `source.ip` and `client.geo`: Investigate the source IP and geography. Look for unusual access from unexpected countries, VPS providers, or anonymizing services.
36+
- `http.request.method`: Determine intent based on HTTP method — `GET` (reading), `POST` (sending), `PATCH`/`DELETE` (modifying/removing messages).
37+
- `token_issued_at` and `@timestamp`: Determine how long the token has been active and whether access is ongoing or recent.
38+
- `azure.graphactivitylogs.properties.c_sid`: Use the session correlation ID to identify other related activity in the same session. This may help identify if the app is accessing multiple users' mailboxes or if the same user is accessing multiple apps.
39+
- Correlate with Microsoft Entra ID (`azure.auditlogs` and `azure.signinlogs`) to determine whether:
40+
- The app was recently granted admin or user consent
41+
- Risky sign-ins occurred just prior to or after mail access
42+
- The same IP or app ID appears across multiple users
43+
44+
### False Positive Analysis
45+
46+
- New legitimate apps may appear after a user consents via OAuth. Developers, third-party tools, or IT-supplied utilities may access mail APIs if users consent.
47+
- Users leveraging Microsoft development environments (e.g., Visual Studio Code) may trigger this behavior with delegated `.default` permissions.
48+
- Admin-approved apps deployed via conditional access may trigger similar access logs if not previously seen in detection baselines.
49+
50+
### Response and Remediation
51+
52+
- If access is unauthorized or unexpected:
53+
- Revoke the app's consent in Azure AD via the Enterprise Applications blade.
54+
- Revoke user refresh tokens via Microsoft Entra or PowerShell.
55+
- Investigate the user's session and alert them to possible phishing or OAuth consent abuse.
56+
- Review and restrict risky OAuth permissions in Conditional Access and App Governance policies.
57+
- Add known, trusted app IDs to a detection allowlist to reduce noise in the future.
58+
- Continue monitoring the app ID for additional usage across the tenant or from suspicious IPs.
59+
"""
60+
references = [
61+
"https://www.volexity.com/blog/2025/04/22/phishing-for-codes-russian-threat-actors-target-microsoft-365-oauth-workflows/",
62+
"https://github.com/dirkjanm/ROADtools",
63+
"https://dirkjanm.io/phishing-for-microsoft-entra-primary-refresh-tokens/",
64+
]
65+
risk_score = 47
66+
rule_id = "e882e934-2aaa-11f0-8272-f661ea17fbcc"
67+
severity = "medium"
68+
tags = [
69+
"Domain: Cloud",
70+
"Data Source: Azure",
71+
"Data Source: Microsoft Graph",
72+
"Data Source: Microsoft Graph Activity Logs",
73+
"Use Case: Threat Detection",
74+
"Tactic: Collection",
75+
"Resources: Investigation Guide",
76+
]
77+
timestamp_override = "event.ingested"
78+
type = "new_terms"
79+
80+
query = '''
81+
event.dataset: "azure.graphactivitylogs" and
82+
azure.graphactivitylogs.properties.app_id: * and
83+
azure.graphactivitylogs.result_signature: 200 and
84+
azure.graphactivitylogs.properties.c_idtyp: "user" and
85+
azure.graphactivitylogs.properties.client_auth_method: 0 and
86+
http.request.method: (GET or POST or PUT or PATCH or DELETE) and (
87+
url.path: (/v1.0/me/*cc or /v1.0/users/*) and
88+
(
89+
url.path: (*mail* or *messages* or *inbox*) or
90+
azure.graphactivitylogs.properties.requestUri: (*mail* or *messages* or *inbox*)
91+
) or
92+
azure.graphactivitylogs.properties.scopes: (
93+
"Mail.Read" or "Mail.ReadWrite" or "Mail.Send" or "email"
94+
)
95+
)
96+
'''
97+
98+
99+
[[rule.threat]]
100+
framework = "MITRE ATT&CK"
101+
[[rule.threat.technique]]
102+
id = "T1114"
103+
name = "Email Collection"
104+
reference = "https://attack.mitre.org/techniques/T1114/"
105+
106+
107+
[rule.threat.tactic]
108+
id = "TA0009"
109+
name = "Collection"
110+
reference = "https://attack.mitre.org/tactics/TA0009/"
111+
112+
[rule.new_terms]
113+
field = "new_terms_fields"
114+
value = [
115+
"azure.graphactivitylogs.properties.app_id",
116+
"azure.graphactivitylogs.properties.user_principal_object_id",
117+
]
118+
[[rule.new_terms.history_window_start]]
119+
field = "history_window_start"
120+
value = "now-14d"
121+
122+

0 commit comments

Comments
 (0)