LoFP LoFP / security teams performing routine audits or assessments that involve retrieving keys or secrets from key vaults may trigger this rule if they perform multiple retrievals in a short time frame.

Techniques

Sample rules

Azure Key Vault Secret Key Usage by Unusual Identity

Description

Identifies secrets, keys, or certificates retrieval operations from Azure Key Vault by a user principal that has not been seen previously doing so in a certain amount of days. Azure Key Vault is a cloud service for securely storing and accessing secrets, keys, and certificates. Unauthorized or excessive retrievals may indicate potential abuse or unauthorized access attempts.

Detection logic

event.dataset : "azure.platformlogs" and
event.outcome: "success" and
event.action : (
    "VaultGet" or
    "KeyGet" or
    "KeyList" or
    "KeyListVersions" or
    "KeyGetDeleted" or
    "KeyListDeleted" or
    "SecretGet" or
    "SecretList" or
    "SecretListVersions" or
    "SecretGetDeleted" or
    "SecretListDeleted" or
    "CertificateGet" or
    "CertificateList" or
    "CertificateListVersions" or
    "CertificateGetDeleted" or
    "CertificateListDeleted" or
    "CertificatePolicyGet" or
    "CertificateContactsGet" or
    "CertificateIssuerGet" or
    "CertificateIssuersList"
) and azure.platformlogs.identity.claim.upn: * and azure.platformlogs.properties.id: *

Excessive Secret or Key Retrieval from Azure Key Vault

Description

Identifies excessive secret or key retrieval operations from Azure Key Vault. This rule detects when a user principal retrieves secrets or keys from Azure Key Vault multiple times within a short time frame, which may indicate potential abuse or unauthorized access attempts. The rule focuses on high-frequency retrieval operations that deviate from normal user behavior, suggesting possible credential harvesting or misuse of sensitive information.

Detection logic

FROM logs-azure.platformlogs-* METADATA _id, _index

// Filter for Azure Key Vault read operations
| WHERE event.dataset == "azure.platformlogs"
  AND event.action IN (
    "VaultGet",
    "KeyGet",
    "KeyList",
    "KeyListVersions",
    "KeyGetDeleted",
    "KeyListDeleted",
    "SecretGet",
    "SecretList",
    "SecretListVersions",
    "SecretGetDeleted",
    "SecretListDeleted",
    "CertificateGet",
    "CertificateList",
    "CertificateListVersions",
    "CertificateGetDeleted",
    "CertificateListDeleted",
    "CertificatePolicyGet",
    "CertificateContactsGet",
    "CertificateIssuerGet",
    "CertificateIssuersList"
  )

// Truncate timestamps into 1-minute windows
| EVAL Esql.time_window.date_trunc = DATE_TRUNC(1 minute, @timestamp)

// Aggregate identity, geo, resource, and activity info
| STATS
    Esql.azure.platformlogs.identity.claim.upn.values = VALUES(azure.platformlogs.identity.claim.upn),
    Esql.azure.platformlogs.identity.claim.upn.count_unique = COUNT_DISTINCT(azure.platformlogs.identity.claim.upn),
    Esql.azure.platformlogs.identity.claim.appid.values = VALUES(azure.platformlogs.identity.claim.appid),
    Esql.azure.platformlogs.identity.claim.objectid.values = VALUES(azure.platformlogs.identity.claim.objectid),

    Esql.source.ip.values = VALUES(source.ip),
    Esql.geo.city.values = VALUES(geo.city_name),
    Esql.geo.region.values = VALUES(geo.region_name),
    Esql.geo.country.values = VALUES(geo.country_name),
    Esql.network.as_org.values = VALUES(source.as.organization.name),

    Esql.event.actions.values = VALUES(event.action),
    Esql.event.count = COUNT(*),
    Esql.event.action.count_distinct = COUNT_DISTINCT(event.action),
    Esql.azure.resource.name.count_distinct = COUNT_DISTINCT(azure.resource.name),
    Esql.azure.resource.name.values = VALUES(azure.resource.name),
    Esql.azure.platformlogs.result_type.values = VALUES(azure.platformlogs.result_type),
    Esql.cloud.region.values = VALUES(cloud.region),

    Esql.agent.name.values = VALUES(agent.name),
    Esql.azure.subscription_id.values = VALUES(azure.subscription_id),
    Esql.azure.resource_group.values = VALUES(azure.resource.group),
    Esql.azure.resource_id.values = VALUES(azure.resource.id)

BY Esql.time_window.date_trunc, azure.platformlogs.identity.claim.upn

// Keep relevant fields
| KEEP
    Esql.time_window.date_trunc,
    Esql.azure.platformlogs.identity.claim.upn.values,
    Esql.azure.platformlogs.identity.claim.upn.count_unique,
    Esql.azure.platformlogs.identity.claim.appid.values,
    Esql.azure.platformlogs.identity.claim.objectid.values,
    Esql.source.ip.values,
    Esql.geo.city.values,
    Esql.geo.region.values,
    Esql.geo.country.values,
    Esql.network.as_org.values,
    Esql.event.actions.values,
    Esql.event.count,
    Esql.event.action.count_distinct,
    Esql.azure.resource.name.count_distinct,
    Esql.azure.resource.name.values,
    Esql.azure.platformlogs.result_type.values,
    Esql.cloud.region.values,
    Esql.agent.name.values,
    Esql.azure.subscription_id.values,
    Esql.azure.resource_group.values,
    Esql.azure.resource_id.values

// Filter for suspiciously high volume of distinct Key Vault reads by a single actor
| WHERE Esql.azure.platformlogs.identity.claim.upn.count_unique == 1 AND Esql.event.count >= 10 AND Esql.event.action.count_distinct >= 2

| SORT Esql.time_window.date_trunc DESC