Multiple Keyvault Mass Secret Retrieval
Query
let query_frequency = 1h;
let query_period = 14d;
let operation_list = dynamic(["VaultGet", "SecretGet", "KeyGet", "CertificateGet"]);
let threshold = 5;
let _EntraIdApps =
_GetWatchlist("UUID-EntraIdApps")
| project AppId, ObjectId, AppDisplayName
;
let _ExpectedRetrieval =
_GetWatchlist("Activity-ExpectedSignificantActivity")
| where Activity == "SecretRetrieval" and (isnotempty(DestinationResource) or isnotempty(Auxiliar))
| project
CallerObjectId = tostring(ActorId),
identity_claim_appid_g = tostring(SourceResource),
Resource = tostring(DestinationResource),
OperationName = tostring(Auxiliar)
;
AzureDiagnostics
| where TimeGenerated > ago(query_period)
| where ResourceType =~ "VAULTS" and OperationName in (operation_list)
| extend
ResultType = column_ifexists("ResultType", ""),
identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g = column_ifexists("identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g", ""),
identity_claim_http_schemas_xmlsoap_org_ws_2005_05_identity_claims_upn_s = column_ifexists("identity_claim_http_schemas_xmlsoap_org_ws_2005_05_identity_claims_upn_s", ""),
identity_claim_oid_g = column_ifexists("identity_claim_oid_g", ""),
identity_claim_upn_s = column_ifexists("identity_claim_upn_s", "")
| extend
CallerObjectId = iff(isempty(identity_claim_oid_g), identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g, identity_claim_oid_g),
CallerObjectUPN = iff(isempty(identity_claim_upn_s), identity_claim_http_schemas_xmlsoap_org_ws_2005_05_identity_claims_upn_s, identity_claim_upn_s)
| join kind=leftanti (
_ExpectedRetrieval
| where isnotempty(Resource)
) on CallerObjectId, identity_claim_appid_g, OperationName, Resource
| join kind=leftanti (
_ExpectedRetrieval
| where isempty(Resource)
) on CallerObjectId, identity_claim_appid_g, OperationName
| summarize arg_min(TimeGenerated, *) by OperationName, requestUri_s, CallerObjectId, _ResourceId
| where TimeGenerated > ago(2 * query_frequency)
| as _Events
| join kind=leftsemi (
_Events
// query_period should be 2 * query_frequency
| evaluate activity_counts_metrics(Type, TimeGenerated, ago(2 * query_frequency), now(), query_frequency, CallerObjectId)
| summarize
arg_min(PreviousTimeGenerated = TimeGenerated, PreviousCount = ["count"]),
arg_max(CurrentTimeGenerated = TimeGenerated, CurrentCount = ["count"])
by CallerObjectId
| where CurrentTimeGenerated > ago(query_period)
| extend PreviousCount = iff(PreviousTimeGenerated == CurrentTimeGenerated, 0, PreviousCount)
| where (not(PreviousCount > threshold) and CurrentCount > threshold)
or ((CurrentCount - PreviousCount) > threshold)
) on CallerObjectId
| extend
requestUri_s = column_ifexists("requestUri_s", ""),
id_s = column_ifexists("id_s", ""),
CallerIPAddress = column_ifexists("CallerIPAddress", ""),
clientInfo_s = column_ifexists("clientInfo_s", "")
| summarize
StartTime = min(TimeGenerated),
EndTime = max(TimeGenerated),
EventCount = count(),
OperationNames = make_set(OperationName, 100),
RequestURIs = make_set(requestUri_s, 100),
CallerIPAddresses = make_set(CallerIPAddress, 100),
clientInfo_s = make_set(clientInfo_s, 100),
take_any(ResourceType, CallerIPAddress, _ResourceId)
by Resource, id_s, CallerObjectId, CallerObjectUPN, identity_claim_appid_g, ResultType
| project-rename AppId = identity_claim_appid_g
| lookup kind=leftouter (
_EntraIdApps
| project AppId = tostring(AppId), AppDisplayName
) on AppId
| lookup kind=leftouter (
union
(
_EntraIdApps
| project ServicePrincipalId = tostring(ObjectId), ServicePrincipalName = tostring(AppDisplayName)
),
(
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(query_period)
),
(
AADManagedIdentitySignInLogs
| where TimeGenerated > ago(query_period)
),
(
AuditLogs
| where TimeGenerated > ago(query_period)
| where Category == "ApplicationManagement" and OperationName has "service principal" and not(AADOperationType in ("Assign", "Unassign"))
| project ServicePrincipalId = tostring(TargetResources[0]["id"]), ServicePrincipalName = tostring(TargetResources[0]["displayName"])
)
| where isnotempty(ServicePrincipalId)
| distinct ServicePrincipalId, ServicePrincipalName
) on $left.CallerObjectId == $right.ServicePrincipalId
| extend CallerObject = coalesce(CallerObjectUPN, ServicePrincipalName)
| project
StartTime,
EndTime,
EventCount,
ResourceType,
Resource,
id_s,
AppId,
AppDisplayName,
CallerObjectId,
CallerObject,
ResultType,
OperationNames,
RequestURIs,
CallerIPAddress,
CallerIPAddresses,
clientInfo_s,
_ResourceIdExplanation
This KQL query is designed to detect unusual activity related to certain operations on Azure Key Vaults over a specified period. Here's a simplified breakdown:
-
Setup Parameters:
- The query checks activities over the last 14 days (
query_period) and evaluates them hourly (query_frequency). - It focuses on specific operations: "VaultGet", "SecretGet", "KeyGet", and "CertificateGet".
- A threshold of 5 is set to identify significant changes in activity.
- The query checks activities over the last 14 days (
-
Data Preparation:
- Two watchlists are used:
_EntraIdApps: Contains application IDs and display names._ExpectedRetrieval: Lists expected significant activities related to secret retrievals.
- Two watchlists are used:
-
Filter and Join Data:
- The query filters logs from Azure Diagnostics for the specified operations on resources of type "VAULTS".
- It extends the data with additional identity information.
- It excludes expected activities by joining with
_ExpectedRetrievalusing aleftantijoin, meaning it keeps only records not found in_ExpectedRetrieval.
-
Detect Anomalies:
- The query identifies events where the operation count exceeds the threshold or shows a significant increase compared to the previous period.
- It uses
activity_counts_metricsto calculate these metrics.
-
Summarize Results:
- It summarizes the data to show the start and end times of detected activities, the number of events, involved operations, request URIs, and caller IP addresses.
- It also retrieves additional information about the caller and application from the watchlists and logs.
-
Final Output:
- The query projects a final set of columns, including the start and end time of the activity, event count, resource details, application and caller information, and operation details.
In essence, this query is designed to identify and summarize unusual or unexpected access patterns to Azure Key Vault resources, focusing on specific operations and significant changes in activity levels.
Details

Jose Sebastián Canós
Released: August 20, 2025
Tables
AzureDiagnostics
AADServicePrincipalSignInLogs
AADManagedIdentitySignInLogs
AuditLogs
Keywords
AzureDiagnosticsVaultsEntraIdAppsActivityExpectedSignificantActivityUUIDAADServicePrincipalSignInLogsAADManagedIdentitySignInLogsAuditLogsApplicationManagementServicePrincipalNameServicePrincipalIdOperationNameResourceTypeResourceAppIdAppDisplayNameCallerObjectIdCallerObjectUPNResultTypeRequestURIsCallerIPAddressCallerIPAddressesClientInfoResourceId
Operators
letdynamicprojectwhereisnotemptyisemptyago=~inextendcolumn_ifexistsiffjoinkind=leftantisummarizearg_minarg_maxasevaluateactivity_counts_metricsnownotonlookupunionhascoalesceproject-renamedistinct