LoFP LoFP / automated scripts or processes that retrieve secrets or keys for legitimate purposes, such as secret rotation or application configuration, may also lead to false positives.

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