Skip to content

Commit bfca0ea

Browse files
[New Hunt] Commvault Supply Chain Threat (#4748)
* hunts for CommVault threat * added lookback time to ESQL query * updated query logic
1 parent 17d98cc commit bfca0ea

7 files changed

+218
-53
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Microsoft Entra ID Rare Service Principal Activity from Multiple IPs
2+
3+
---
4+
5+
## Metadata
6+
7+
- **Author:** Elastic
8+
- **Description:** This hunting query identifies service principal activity across Microsoft Entra ID, Microsoft 365, and Graph API logs that is both rare and originates from multiple IP addresses. Adversaries may abuse service principals to persist access, move laterally, or access sensitive APIs. This hunt surfaces service principals performing unusual or infrequent actions from more than one IP, which could indicate credential misuse or stolen token replay.
9+
- **UUID:** `91f4e8e6-7d35-45e1-89c5-8c77e78ef5c1`
10+
- **Integration:** [azure](https://docs.elastic.co/integrations/azure), [o365](https://docs.elastic.co/integrations/o365)
11+
- **Language:** `[ES|QL]`
12+
- **Source File:** [Microsoft Entra ID Rare Service Principal Activity from Multiple IPs](../queries/entra_rare_actions_by_service_principal.toml)
13+
14+
## Query
15+
16+
```sql
17+
FROM logs-azure.*, logs-o365.audit-*
18+
| WHERE @timestamp > now() - 30 day
19+
| WHERE
20+
event.dataset in ("azure.auditlogs", "azure.signinlogs", "o365.audit", "azure.graphactivitylogs")
21+
AND (
22+
(azure.signinlogs.properties.service_principal_name IS NOT NULL OR
23+
azure.auditlogs.properties.initiated_by.app.servicePrincipalId IS NOT NULL OR
24+
azure.graphactivitylogs.properties.service_principal_id IS NOT NULL) OR
25+
`o365`.audit.ExtendedProperties.extendedAuditEventCategory == "ServicePrincipal"
26+
)
27+
| EVAL
28+
service_principal_name = COALESCE(
29+
azure.auditlogs.properties.initiated_by.app.displayName,
30+
azure.signinlogs.properties.service_principal_name,
31+
`o365`.audit.UserId
32+
),
33+
service_principal_id = COALESCE(
34+
azure.auditlogs.properties.initiated_by.app.servicePrincipalId,
35+
azure.graphactivitylogs.properties.service_principal_id,
36+
`o365`.audit.UserId,
37+
azure.signinlogs.properties.service_principal_id
38+
),
39+
timestamp_day_bucket = DATE_TRUNC(1 day, @timestamp)
40+
| WHERE source.ip IS NOT NULL
41+
// filter for unexpected service principal and IP address patterns
42+
// OR NOT CIDR_MATCH(source.ip, "127.0.0.2/32")
43+
| STATS
44+
event_count = COUNT(),
45+
ips = VALUES(source.ip),
46+
distinct_ips = COUNT_DISTINCT(source.ip),
47+
datasets = VALUES(event.dataset),
48+
service_principal_ids = VALUES(service_principal_id),
49+
event_actions = VALUES(event.action),
50+
daily_action_count = COUNT()
51+
BY event.action, service_principal_name, timestamp_day_bucket
52+
| WHERE (daily_action_count <= 5 and distinct_ips >= 2)
53+
| SORT daily_action_count ASC
54+
```
55+
56+
## Notes
57+
58+
- This is an ES|QL query returning results in a tabular format. Analysts should pivot from any column value (e.g., `event.action`, `service_principal_name`, `service_principal_id`, or `source.ip`) into raw event data to inspect the full scope of the activity.
59+
- This hunt looks for service principals performing rare or low-frequency actions (≤ 5 per day) from multiple IPs (≥ 2), which could indicate replayed tokens, stolen credentials, or unusual automation.
60+
- The `service_principal_name` field is populated using the display name or user ID, depending on the log source.
61+
- The `service_principal_id` is used to correlate actions across datasets such as Azure Audit Logs, Sign-In Logs, M365 Audit Logs, and Graph Activity Logs.
62+
- Check the `source.ip` field for anomalies in geolocation or ASN. If the same SP is used from geographically distant locations or via unexpected ISPs, this may indicate compromise.
63+
- Review the `event.action` field to determine what the service principal was doing — uncommon API calls, login attempts, resource creation, or changes should be reviewed.
64+
- Rare service principal behavior may be legitimate (e.g., new integration) but should always be validated against expected automation and deployment activity.
65+
- This technique has been observed in attacks involving abuse of OAuth apps, Microsoft Graph API access, and stolen tokens for lateral movement or persistent access.
66+
67+
## MITRE ATT&CK Techniques
68+
69+
- [T1098.001](https://attack.mitre.org/techniques/T1098/001)
70+
71+
## References
72+
73+
- https://www.cisa.gov/news-events/alerts/2025/05/22/advisory-update-cyber-threat-activity-targeting-commvaults-saas-cloud-application-metallic
74+
75+
## License
76+
77+
- `Elastic License v2`

hunting/azure/docs/entra_service_principal_credentials_added_to_rare_app.md

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Microsoft Entra ID Credentials Added to Rare Service Principal
1+
# Microsoft Entra ID Uncommon IP Adding Credentials to Service Principal
22

33
---
44

@@ -9,44 +9,55 @@
99
- **UUID:** `d2dd0288-0a8c-11f0-b738-f661ea17fbcc`
1010
- **Integration:** [azure](https://docs.elastic.co/integrations/azure)
1111
- **Language:** `[ES|QL]`
12-
- **Source File:** [Microsoft Entra ID Credentials Added to Rare Service Principal](../queries/entra_service_principal_credentials_added_to_rare_app.toml)
12+
- **Source File:** [Microsoft Entra ID Uncommon IP Adding Credentials to Service Principal](../queries/entra_service_principal_credentials_added_to_rare_app.toml)
1313

1414
## Query
1515

1616
```sql
1717
FROM logs-azure.auditlogs*
18+
| WHERE @timestamp > now() - 60 day
1819
| WHERE
19-
// filter on Microsoft Entra Audit Logs
20-
// filter for service principal credentials being added
2120
event.dataset == "azure.auditlogs"
22-
and azure.auditlogs.operation_name == "Add service principal credentials"
23-
and event.outcome == "success"
21+
AND azure.auditlogs.operation_name == "Add service principal credentials"
22+
AND event.outcome == "success"
2423
| EVAL
25-
// SLICE n0 of requests values for specific Client App ID
26-
// Cast Client App ID to STRING type
24+
// Extract appId from additional_details
2725
azure.auditlogs.properties.additional_details.appId = MV_SLICE(azure.auditlogs.properties.additional_details.value, 0)::STRING
2826
| WHERE
29-
// REGEX on Client APP ID for UUIDv4
27+
// Ensure appId is UUIDv4 format
3028
azure.auditlogs.properties.additional_details.appId RLIKE """[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"""
29+
// Use the below filter to limit results to credential additions associated with known service principals (e.g. Commvault)
30+
// AND (azure.auditlogs.properties.target_resources.`0`.modified_properties.`0`.new_value LIKE "*Commvault*" OR azure.auditlogs.properties.target_resources.`0`.modified_properties.`0`.old_value LIKE "*Commvault*")
3131
| EVAL
32-
// BUCKET events weekly
32+
// Bucket events by each week
3333
timestamp_week_bucket = DATE_TRUNC(7 day, @timestamp)
3434
| STATS
35-
// Aggregate weekly occurrences by Client App ID, User ID
36-
weekly_user_app_occurrence_count = COUNT_DISTINCT(timestamp_week_bucket) BY
37-
azure.auditlogs.properties.additional_details.appId,
38-
azure.auditlogs.properties.initiated_by.user.id
39-
| WHERE weekly_user_app_occurrence_count == 1
35+
operation = VALUES(azure.auditlogs.operation_name),
36+
app_id = VALUES(azure.auditlogs.properties.additional_details.appId),
37+
correlation_id = VALUES(azure.auditlogs.properties.correlation_id),
38+
identity = VALUES(azure.auditlogs.properties.identity),
39+
initiated_by_id = VALUES(azure.auditlogs.properties.initiated_by.user.id),
40+
user_principal_name = VALUES(azure.auditlogs.properties.initiated_by.user.userPrincipalName),
41+
tenant_id = VALUES(azure.auditlogs.properties.tenantId),
42+
modified_properties_new = VALUES(azure.auditlogs.properties.target_resources.`0`.modified_properties.`0`.new_value),
43+
modified_properties_old = VALUES(azure.auditlogs.properties.target_resources.`0`.modified_properties.`0`.old_value),
44+
weekly_occurrence_count = COUNT_DISTINCT(timestamp_week_bucket)
45+
BY source.ip, azure.auditlogs.properties.additional_details.appId
46+
| WHERE weekly_occurrence_count <= 5
4047
```
4148

4249
## Notes
4350

44-
- This is an ES|QL query, therefore results are returned in a tabular format. Pivot into related events using the `azure.auditlogs.properties.initiated_by.user.id`
45-
- Review `azure.auditlogs.properties.additional_details.appId` to verify the Client App ID. This should be a known application in your environment. Check if it is an Azure-managed application, custom application, or a third-party application.
46-
- The `azure.auditlogs.properties.additional_details.appId` value will be available in `azure.auditlogs.properties.additional_details.value` when triaging the original events.
47-
- The `azure.auditlogs.properties.initiated_by.user.id` may be a hijacked account with elevated privileges. Review the user account to determine if it is a known administrative account or a compromised account.
48-
- Review `azure.auditlogs.properties.target_resources.0.display_name` to verify the service principal name. This correlates directly to the `azure.auditlogs.properties.additional_details.appId` value.
49-
- Identify potential authentication events from the service principal the credentials were added to. This may indicate that the service principal is being used to access resources in your environment.
51+
- This is an ES|QL query returning results in a tabular format. Analysts should pivot from any column value (e.g., `app_id`, `initiated_by_id`, `source.ip`, or `correlation_id`) into raw event data to inspect the full scope of the activity.
52+
- The operation `Add service principal credentials` indicates a credential (e.g., password or certificate) was added to a service principal. This is often legitimate but can be abused for persistence, especially if the service principal was compromised or created by a threat actor.
53+
- Investigate the value of `azure.auditlogs.properties.additional_details.appId`. Determine whether this service principal belongs to a Microsoft-managed application, a known third-party tool like Commvault, or an unknown application.
54+
- Review `azure.auditlogs.properties.target_resources.0.display_name` or its equivalent in the raw logs to verify the name of the service principal receiving credentials.
55+
- Examine `modified_properties_new` and `modified_properties_old` to understand how many credentials were added. Look for suspicious patterns, such as multiple credentials added at once or display names like `Commvault`.
56+
- Pivot on the `initiated_by_id` and `user_principal_name` to determine if the activity was expected or if the account may be compromised.
57+
- Check the `source.ip` for geolocation, VPN/proxy usage, or unfamiliar ISP origin. Uncommon IPs for specific 3rd-party service principals may indicate adversarial activity.
58+
- A low `weekly_occurrence_count` (e.g., 1) suggests the activity is rare for the given service principal and IP, making it worthy of further investigation.
59+
- Review activity linked via any of the `correlation_id` values to see what actions followed credential addition. This may include sign-ins, Graph API calls, or resource access.
60+
- Search for downstream activity from the `app_id`, such as token usage, service principal logins, or cloud resource actions that may indicate abuse or persistence.
5061

5162
## MITRE ATT&CK Techniques
5263

@@ -56,6 +67,7 @@ FROM logs-azure.auditlogs*
5667

5768
- https://cloud.google.com/blog/topics/threat-intelligence/remediation-and-hardening-strategies-for-microsoft-365-to-defend-against-unc2452
5869
- https://dirkjanm.io/azure-ad-privilege-escalation-application-admin/
70+
- https://www.cisa.gov/news-events/alerts/2025/05/22/advisory-update-cyber-threat-activity-targeting-commvaults-saas-cloud-application-metallic
5971

6072
## License
6173

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
[hunt]
2+
author = "Elastic"
3+
description = """This hunting query identifies service principal activity across Microsoft Entra ID, Microsoft 365, and Graph API logs that is both rare and originates from multiple IP addresses. Adversaries may abuse service principals to persist access, move laterally, or access sensitive APIs. This hunt surfaces service principals performing unusual or infrequent actions from more than one IP, which could indicate credential misuse or stolen token replay."""
4+
integration = ["azure", "o365"]
5+
uuid = "91f4e8e6-7d35-45e1-89c5-8c77e78ef5c1"
6+
name = "Microsoft Entra ID Rare Service Principal Activity from Multiple IPs"
7+
language = ["ES|QL"]
8+
license = "Elastic License v2"
9+
notes = [
10+
"This is an ES|QL query returning results in a tabular format. Analysts should pivot from any column value (e.g., `event.action`, `service_principal_name`, `service_principal_id`, or `source.ip`) into raw event data to inspect the full scope of the activity.",
11+
"This hunt looks for service principals performing rare or low-frequency actions (≤ 5 per day) from multiple IPs (≥ 2), which could indicate replayed tokens, stolen credentials, or unusual automation.",
12+
"The `service_principal_name` field is populated using the display name or user ID, depending on the log source.",
13+
"The `service_principal_id` is used to correlate actions across datasets such as Azure Audit Logs, Sign-In Logs, M365 Audit Logs, and Graph Activity Logs.",
14+
"Check the `source.ip` field for anomalies in geolocation or ASN. If the same SP is used from geographically distant locations or via unexpected ISPs, this may indicate compromise.",
15+
"Review the `event.action` field to determine what the service principal was doing — uncommon API calls, login attempts, resource creation, or changes should be reviewed.",
16+
"Rare service principal behavior may be legitimate (e.g., new integration) but should always be validated against expected automation and deployment activity.",
17+
"This technique has been observed in attacks involving abuse of OAuth apps, Microsoft Graph API access, and stolen tokens for lateral movement or persistent access."
18+
]
19+
mitre = ['T1098.001']
20+
references = [
21+
"https://www.cisa.gov/news-events/alerts/2025/05/22/advisory-update-cyber-threat-activity-targeting-commvaults-saas-cloud-application-metallic"
22+
]
23+
query = [
24+
'''
25+
FROM logs-azure.*, logs-o365.audit-*
26+
| WHERE @timestamp > now() - 30 day
27+
| WHERE
28+
event.dataset in ("azure.auditlogs", "azure.signinlogs", "o365.audit", "azure.graphactivitylogs")
29+
AND (
30+
(azure.signinlogs.properties.service_principal_name IS NOT NULL OR
31+
azure.auditlogs.properties.initiated_by.app.servicePrincipalId IS NOT NULL OR
32+
azure.graphactivitylogs.properties.service_principal_id IS NOT NULL) OR
33+
`o365`.audit.ExtendedProperties.extendedAuditEventCategory == "ServicePrincipal"
34+
)
35+
| EVAL
36+
service_principal_name = COALESCE(
37+
azure.auditlogs.properties.initiated_by.app.displayName,
38+
azure.signinlogs.properties.service_principal_name,
39+
`o365`.audit.UserId
40+
),
41+
service_principal_id = COALESCE(
42+
azure.auditlogs.properties.initiated_by.app.servicePrincipalId,
43+
azure.graphactivitylogs.properties.service_principal_id,
44+
`o365`.audit.UserId,
45+
azure.signinlogs.properties.service_principal_id
46+
),
47+
timestamp_day_bucket = DATE_TRUNC(1 day, @timestamp)
48+
| WHERE source.ip IS NOT NULL
49+
// filter for unexpected service principal and IP address patterns
50+
// OR NOT CIDR_MATCH(source.ip, "127.0.0.2/32")
51+
| STATS
52+
event_count = COUNT(),
53+
ips = VALUES(source.ip),
54+
distinct_ips = COUNT_DISTINCT(source.ip),
55+
datasets = VALUES(event.dataset),
56+
service_principal_ids = VALUES(service_principal_id),
57+
event_actions = VALUES(event.action),
58+
daily_action_count = COUNT()
59+
BY event.action, service_principal_name, timestamp_day_bucket
60+
| WHERE (daily_action_count <= 5 and distinct_ips >= 2)
61+
| SORT daily_action_count ASC
62+
'''
63+
]

0 commit comments

Comments
 (0)