Detection rules › Kusto

Mass secret retrieval from Azure Key Vault

Status
available
Severity
low
Time window
1d
Group by
CallerObjectId, CallerObjectUPN, ResourceType, ResultType, identity_claim_appid_g
Source
github.com/Azure/Azure-Sentinel

'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

TacticTechniques
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 =
ifisempty(identity_claim_oid_g)identity_claim_http_schemas_microsoft_com_identity_claims_objectidentifier_g
elseidentity_claim_oid_g
CallerObjectUPN =
ifisempty(identity_claim_upn_s)identity_claim_http_schemas_xmlsoap_org_ws_2005_05_identity_claims_upn_s
elseidentity_claim_upn_s

Stage 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.

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
CallerIPListsummarize
CallerIPMaxsummarize
CallerObjectIdsummarize
CallerObjectUPNsummarize
EndTimesummarize
EventCountsummarize
OperationNameListsummarize
RequestURLListsummarize
ResourceIdsummarize
ResourceListsummarize
ResourceTypesummarize
ResultTypesummarize
StartTimesummarize
clientInfo_sListsummarize
identity_claim_appid_gsummarize
timestampextend