Detection rules › Kusto
Mass secret retrieval from Azure Key Vault
'Identifies mass secret retrieval from Azure Key Vault observed by a single user. Mass secret retrival crossing a certain threshold is an indication of credential dump operations or mis-configured applications. You can tweak the EventCountThreshold based on average count seen in your environment and also filter any known sources (IP/Account) and useragent combinations based on historical analysis to further reduce noise'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Rule body
id: 24f8c234-d1ff-40ec-8b73-96b17a3a9c1c
name: Mass secret retrieval from Azure Key Vault
description: |
'Identifies mass secret retrieval from Azure Key Vault observed by a single user.
Mass secret retrival crossing a certain threshold is an indication of credential dump operations or mis-configured applications.
You can tweak the EventCountThreshold based on average count seen in your environment and also filter any known sources (IP/Account) and useragent combinations based on historical analysis to further reduce noise'
severity: Low
status: Available
requiredDataConnectors:
- connectorId: AzureKeyVault
dataTypes:
- KeyVaultData
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1003
query: |
let DistinctSecretsThreshold = 10;
let EventCountThreshold = 50;
// To avoid any False Positives, filtering using AppId is recommended.
// The AppId 509e4652-da8d-478d-a730-e9d4a1996ca4 has been added in the query as it corresponds to Azure Resource Graph performing VaultGet operations for indexing and syncing all tracked resources across Azure.
// The AppId 8cae6e77-e04e-42ce-b5cb-50d82bce26b1 has been added as it correspond to Microsoft Policy Insights Provider Data Plane performing VaultGet operations for policies checks.
let AllowedAppId = dynamic(["509e4652-da8d-478d-a730-e9d4a1996ca4","8cae6e77-e04e-42ce-b5cb-50d82bce26b1"]);
let OperationList = dynamic(["SecretGet", "KeyGet", "VaultGet"]);
AzureDiagnostics
| where OperationName in (OperationList) and ResourceType =~ "VAULTS"
| where not(identity_claim_appid_g in (AllowedAppId) and OperationName == 'VaultGet')
| extend
ResourceId,
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)
| as _Retrievals
| where CallerObjectId in (toscalar(
_Retrievals
| where ResultType == "Success"
| summarize Count = dcount(requestUri_s) by OperationName, CallerObjectId
| where Count > DistinctSecretsThreshold
| summarize make_set(CallerObjectId,10000)
))
| extend
requestUri_s = column_ifexists("requestUri_s", ""),
id_s = column_ifexists("id_s", ""),
CallerIPAddress = column_ifexists("CallerIPAddress", ""),
clientInfo_s = column_ifexists("clientInfo_s", "")
| summarize
EventCount = count(),
StartTime = min(TimeGenerated),
EndTime = max(TimeGenerated),
ResourceList = make_set(Resource, 50),
OperationNameList = make_set(OperationName, 50),
RequestURLList = make_set(requestUri_s, 50),
ResourceId = max(ResourceId),
CallerIPList = make_set(CallerIPAddress, 50),
clientInfo_sList = make_set(clientInfo_s, 50),
CallerIPMax = max(CallerIPAddress)
by ResourceType, ResultType, identity_claim_appid_g, CallerObjectId, CallerObjectUPN
| where EventCount > EventCountThreshold
| project-reorder StartTime, EndTime, EventCount, ResourceId,ResourceType,identity_claim_appid_g, CallerObjectId, CallerObjectUPN, ResultType, ResourceList, OperationNameList, RequestURLList, CallerIPList, clientInfo_sList
| extend timestamp = EndTime
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: CallerObjectId
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CallerIPMax
version: 1.0.8
kind: Scheduled
Stages and Predicates
Parameters
let DistinctSecretsThreshold = 10;
let EventCountThreshold = 50;
let AllowedAppId = dynamic(["509e4652-da8d-478d-a730-e9d4a1996ca4","8cae6e77-e04e-42ce-b5cb-50d82bce26b1"]);
let OperationList = dynamic(["SecretGet", "KeyGet", "VaultGet"]);
Stage 1: source
AzureDiagnostics
Stage 2: where
| where OperationName in (OperationList) and ResourceType =~ "VAULTS"
Stage 3: where
| where not(identity_claim_appid_g in (AllowedAppId) and OperationName == 'VaultGet')
Stage 4: extend
| extend
ResourceId,
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", "")
Stage 5: extend
| 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)
CallerObjectId =if
isempty(identity_claim_oid_g)identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_gelse
identity_claim_oid_gCallerObjectUPN =if
isempty(identity_claim_upn_s)identity_claim_http_schemas_xmlsoap_org_ws_2005_05_identity_claims_upn_selse
identity_claim_upn_sStage 6: kusto:as
| as _Retrievals
Stage 7: where
| where CallerObjectId in (toscalar(
_Retrievals
| where ResultType == "Success"
| summarize Count = dcount(requestUri_s) by OperationName, CallerObjectId
| where Count > DistinctSecretsThreshold
| summarize make_set(CallerObjectId,10000)
))
Stage 8: extend
| extend
requestUri_s = column_ifexists("requestUri_s", ""),
id_s = column_ifexists("id_s", ""),
CallerIPAddress = column_ifexists("CallerIPAddress", ""),
clientInfo_s = column_ifexists("clientInfo_s", "")
Stage 9: summarize
| summarize
EventCount = count(),
StartTime = min(TimeGenerated),
EndTime = max(TimeGenerated),
ResourceList = make_set(Resource, 50),
OperationNameList = make_set(OperationName, 50),
RequestURLList = make_set(requestUri_s, 50),
ResourceId = max(ResourceId),
CallerIPList = make_set(CallerIPAddress, 50),
clientInfo_sList = make_set(clientInfo_s, 50),
CallerIPMax = max(CallerIPAddress)
by ResourceType, ResultType, identity_claim_appid_g, CallerObjectId, CallerObjectUPN
Stage 10: where
| where EventCount > EventCountThreshold
Stage 11: project-reorder
| project-reorder StartTime, EndTime, EventCount, ResourceId,ResourceType,identity_claim_appid_g, CallerObjectId, CallerObjectUPN, ResultType, ResourceList, OperationNameList, RequestURLList, CallerIPList, clientInfo_sList
Stage 12: extend
| extend timestamp = EndTime
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
OperationName | eq | VaultGet | excludes:OperationName field:"OperationName" value:"VaultGet" |
identity_claim_appid_g | in | 509e4652-da8d-478d-a730-e9d4a1996ca4, 8cae6e77-e04e-42ce-b5cb-50d82bce26b1 | excludes:identity_claim_appid_g field:"identity_claim_appid_g" value:"509e4652-da8d-478d-a730-e9d4a1996ca4" field:"identity_claim_appid_g" value:"8cae6e77-e04e-42ce-b5cb-50d82bce26b1" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventCount | gt |
| field:"EventCount" kind:gt value:"50" |
OperationName | in |
| field:"OperationName" kind:in |
ResourceType | eq |
| field:"ResourceType" kind:eq value:"VAULTS" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
CallerIPList | summarize |
CallerIPMax | summarize |
CallerObjectId | summarize |
CallerObjectUPN | summarize |
EndTime | summarize |
EventCount | summarize |
OperationNameList | summarize |
RequestURLList | summarize |
ResourceId | summarize |
ResourceList | summarize |
ResourceType | summarize |
ResultType | summarize |
StartTime | summarize |
clientInfo_sList | summarize |
identity_claim_appid_g | summarize |
timestamp | extend |