|
| 1 | +[metadata] |
| 2 | +creation_date = "2025/12/01" |
| 3 | +integration = ["aws", "gcp", "azure"] |
| 4 | +maturity = "production" |
| 5 | +updated_date = "2025/12/01" |
| 6 | + |
| 7 | +[rule] |
| 8 | +author = ["Elastic"] |
| 9 | +description = """ |
| 10 | +This rule detects authenticated sessions accessing secret stores across multiple cloud providers from the same source |
| 11 | +address within a short period of time. Adversaries with access to compromised credentials or session tokens may attempt |
| 12 | +to retrieve secrets from services such as AWS Secrets Manager, Google Secret Manager, or Azure Key Vault in rapid |
| 13 | +succession to expand their access or exfiltrate sensitive information. |
| 14 | +""" |
| 15 | +from = "now-9m" |
| 16 | +interval = "5m" |
| 17 | +language = "esql" |
| 18 | +license = "Elastic License v2" |
| 19 | +name = "Multiple Cloud Secrets Accessed by Source Address" |
| 20 | +note = """## Triage and analysis |
| 21 | +
|
| 22 | +### Multiple Cloud Secrets Accessed by Source Address |
| 23 | +
|
| 24 | +This alert identifies a single source IP address accessing secret-management APIs across **multiple cloud providers** |
| 25 | +(e.g., AWS Secrets Manager, Google Secret Manager, Azure Key Vault) within a short timeframe. |
| 26 | +This behavior is strongly associated with **credential theft, session hijacking, or token replay**, where an adversary |
| 27 | +uses stolen authenticated sessions to harvest secrets across cloud environments. |
| 28 | +
|
| 29 | +Unexpected cross-cloud secret retrieval is uncommon and typically indicates automation misuse or malicious activity. |
| 30 | +
|
| 31 | +### Possible investigation steps |
| 32 | +
|
| 33 | +- Validate the principal |
| 34 | + - Identify the user, service account, workload identity, or application making the requests. |
| 35 | + - Confirm whether this identity is expected to operate across more than one cloud provider. |
| 36 | +- Review related activity |
| 37 | + - Look for additional alerts involving the same identity, source IP, or token over the last 24–48 hours. |
| 38 | + - Identify whether the source IP has been observed performing unusual authentication, privilege escalation, |
| 39 | + or reconnaissance. |
| 40 | +- Check application or service context |
| 41 | + - Determine whether any workload legitimately pulls secrets from multiple cloud providers. |
| 42 | + - Review deployment pipelines or integration layers that might legitimately bridge AWS, Azure, and GCP. |
| 43 | +- Analyze user agent and invocation patterns |
| 44 | + - Compare `user_agent.original` or equivalent fields against expected SDKs or automation tools. |
| 45 | + - Suspicious indicators include CLI tools, unknown libraries, browser user agents, or custom scripts. |
| 46 | +- Inspect IP reputation and origin |
| 47 | + - Determine whether the source IP corresponds to a managed workload (EC2, GCE, Azure VM) or an unexpected host. |
| 48 | + - Validate that the associated instance or host is under your control and behaving normally. |
| 49 | +- Review IAM permissions and accessed secrets |
| 50 | + - Check the policies attached to the identity. |
| 51 | + - Verify whether the accessed secrets are sensitive, unused, or unrelated to the identity’s purpose. |
| 52 | +- Assess potential compromise scope |
| 53 | + - If compromise is suspected, enumerate other assets accessed by the same identity in the last 24 hours. |
| 54 | + - Look for lateral movement, privilege escalation, or abnormal API usage. |
| 55 | +
|
| 56 | +### False positive analysis |
| 57 | +
|
| 58 | +- Validate whether the source IP is associated with a legitimate multi-cloud orchestration tool, automation pipeline, |
| 59 | + or shared CI/CD system. |
| 60 | +- Confirm that the identity is authorized to access secrets across multiple cloud services. |
| 61 | +- If activity is expected, consider adding exceptions that pair account identity, source IP, and expected user agent |
| 62 | + to reduce noise. |
| 63 | +
|
| 64 | +### Response and remediation |
| 65 | +
|
| 66 | +- Initiate incident response** if the activity is unauthorized or suspicious. |
| 67 | +- Restrict or disable** the affected credentials or service accounts. |
| 68 | +- Rotate all accessed secrets** and review other secrets the identity can access. |
| 69 | +- Analyze systems** that may have leaked credentials, such as compromised hosts or exposed tokens. |
| 70 | +- Harden identity security: |
| 71 | + - Enforce MFA for users where applicable. |
| 72 | + - Reduce permissions to least privilege. |
| 73 | + - Review trust relationships, workload identities, and cross-cloud integrations. |
| 74 | +- Search for persistence mechanisms** such as newly created keys, roles, or service accounts. |
| 75 | +- Improve monitoring and audit visibility** by ensuring logging is enabled across all cloud environments. |
| 76 | +- Determine root cause** (phishing, malware, token replay, exposed credential, etc.) and close the vector to prevent recurrence. |
| 77 | +""" |
| 78 | +references = [ |
| 79 | + "https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html", |
| 80 | + "https://docs.cloud.google.com/secret-manager/docs/samples/secretmanager-access-secret-version", |
| 81 | + "https://learn.microsoft.com/en-us/azure/key-vault/secrets/about-secrets", |
| 82 | + "https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack", |
| 83 | +] |
| 84 | +risk_score = 73 |
| 85 | +rule_id = "472b4944-d810-43cf-83dc-7d080ae1b8dd" |
| 86 | +setup = """ |
| 87 | +This multi-datasource rule relies on additional configurations from each hyperscaler. |
| 88 | +
|
| 89 | +- GCP Audit: [Enable DATA_READ for the Secret Manager API service](https://docs.cloud.google.com/logging/docs/audit/configure-data-access) |
| 90 | +- Azure: [Enable Diagnostic Logging for the Key Vault Service](https://learn.microsoft.com/en-us/azure/key-vault/general/howto-logging?tabs=azure-cli) |
| 91 | +- AWS: Secrets Manager read access is automatically logged by CloudTrail. |
| 92 | +""" |
| 93 | +severity = "high" |
| 94 | +tags = [ |
| 95 | + "Domain: Cloud", |
| 96 | + "Domain: IAM", |
| 97 | + "Domain: Storage", |
| 98 | + "Data Source: AWS", |
| 99 | + "Data Source: Amazon Web Services", |
| 100 | + "Data Source: AWS Secrets Manager", |
| 101 | + "Data Source: Azure", |
| 102 | + "Data Source: Azure Activity Logs", |
| 103 | + "Data Source: GCP", |
| 104 | + "Data Source: Google Cloud Platform", |
| 105 | + "Tactic: Credential Access", |
| 106 | + "Resources: Investigation Guide", |
| 107 | +] |
| 108 | +timestamp_override = "event.ingested" |
| 109 | +type = "esql" |
| 110 | + |
| 111 | +query = ''' |
| 112 | +FROM logs-azure.platformlogs-*, logs-azure.activitylogs-*, logs-aws.cloudtrail-*, logs-gcp.audit-* METADATA _id, _version, _index |
| 113 | +| WHERE |
| 114 | + ( |
| 115 | + /* AWS Secrets Manager */ |
| 116 | + (event.dataset == "aws.cloudtrail" AND event.provider == "secretsmanager.amazonaws.com" AND event.action == "GetSecretValue") OR |
| 117 | + // Azure Key Vault (platform logs) |
| 118 | + (event.dataset == "azure.platformlogs" AND event.action IN ("SecretGet", "KeyGet")) or |
| 119 | + /* Azure Key Vault (activity logs) */ |
| 120 | + (event.dataset == "azure.activitylogs" AND azure.activitylogs.operation_name IN ("MICROSOFT.KEYVAULT/VAULTS/SECRETS/LIST", "MICROSOFT.KEYVAULT/VAULTS/SECRETS/GET")) OR |
| 121 | + /* Azure Managed HSM secret */ |
| 122 | + (event.dataset == "azure.activitylogs" AND azure.activitylogs.operation_name LIKE "MICROSOFT.KEYVAULT/managedHSM/keys/*") OR |
| 123 | + /* Google Secret Manager */ |
| 124 | + (event.dataset IN ("googlecloud.audit", "gcp.audit") AND |
| 125 | + event.action IN ("google.cloud.secretmanager.v1.SecretManagerService.AccessSecretVersion", "google.cloud.secretmanager.v1.SecretManagerService.GetSecretRequest")) |
| 126 | + ) AND source.ip IS NOT NULL |
| 127 | +// Unified user identity (raw) |
| 128 | +| EVAL Esql_priv.user_id = |
| 129 | + COALESCE( |
| 130 | + client.user.id, |
| 131 | + aws.cloudtrail.user_identity.arn, |
| 132 | + azure.platformlogs.identity.claim.upn, |
| 133 | + NULL |
| 134 | + ) |
| 135 | +// Cloud vendor label based on dataset |
| 136 | +| EVAL Esql.cloud_vendor = CASE( |
| 137 | + event.dataset == "aws.cloudtrail", "aws", |
| 138 | + event.dataset IN ("azure.platformlogs","azure.activitylogs"), "azure", |
| 139 | + event.dataset IN ("googlecloud.audit","gcp.audit"), "gcp", |
| 140 | + "unknown" |
| 141 | + ) |
| 142 | +// Vendor+tenant label, e.g. aws:123456789012, azure:tenant, gcp:project |
| 143 | +| EVAL Esql.tenant_label = CASE( |
| 144 | + Esql.cloud_vendor == "aws", CONCAT("aws:", cloud.account.id), |
| 145 | + Esql.cloud_vendor == "azure", CONCAT("azure:", cloud.account.id), |
| 146 | + Esql.cloud_vendor == "gcp", CONCAT("gcp:", cloud.account.id), |
| 147 | + NULL |
| 148 | + ) |
| 149 | +| STATS |
| 150 | + // Core counts |
| 151 | + Esql.events_count = COUNT(*), |
| 152 | + Esql.vendor_count_distinct = COUNT_DISTINCT(Esql.cloud_vendor), |
| 153 | + // Action & data source context |
| 154 | + Esql.event_action_values = VALUES(event.action), |
| 155 | + Esql.data_source_values = VALUES(event.dataset), |
| 156 | + // Cloud vendor + tenant context |
| 157 | + Esql.cloud_vendor_values = VALUES(Esql.cloud_vendor), |
| 158 | + Esql.tenant_label_values = VALUES(Esql.tenant_label), |
| 159 | + // Hyperscaler-specific IDs |
| 160 | + Esql.aws_account_id_values = VALUES(CASE(Esql.cloud_vendor == "aws", cloud.account.id, NULL)), |
| 161 | + Esql.azure_tenant_id_values = VALUES(CASE(Esql.cloud_vendor == "azure", cloud.account.id, NULL)), |
| 162 | + Esql.gcp_project_id_values = VALUES(CASE(Esql.cloud_vendor == "gcp", cloud.account.id, NULL)), |
| 163 | + // Generic cloud metadata |
| 164 | + Esql.cloud_region_values = VALUES(cloud.region), |
| 165 | + Esql.cloud_service_name_values = VALUES(cloud.service.name), |
| 166 | + // Identity (privileged) |
| 167 | + Esql_priv.user_values = VALUES(Esql_priv.user_id), |
| 168 | + Esql_priv.client_user_id_values = VALUES(client.user.id), |
| 169 | + Esql_priv.aws_user_identity_arn_values = VALUES(aws.cloudtrail.user_identity.arn), |
| 170 | + Esql_priv.azure_upn_values = VALUES(azure.platformlogs.identity.claim.upn), |
| 171 | + // Namespace values |
| 172 | + Esql.data_stream_namespace_values = VALUES(data_stream.namespace) |
| 173 | + BY source.ip |
| 174 | +// Require multi-vendor cred-access from same source IP |
| 175 | +| WHERE Esql.vendor_count_distinct >= 2 |
| 176 | +| SORT Esql.events_count DESC |
| 177 | +| KEEP Esql.*, Esql_priv.*, source.ip |
| 178 | +''' |
| 179 | + |
| 180 | + |
| 181 | + |
| 182 | +[[rule.threat]] |
| 183 | +framework = "MITRE ATT&CK" |
| 184 | +[[rule.threat.technique]] |
| 185 | +id = "T1555" |
| 186 | +name = "Credentials from Password Stores" |
| 187 | +reference = "https://attack.mitre.org/techniques/T1555/" |
| 188 | +[[rule.threat.technique.subtechnique]] |
| 189 | +id = "T1555.006" |
| 190 | +name = "Cloud Secrets Management Stores" |
| 191 | +reference = "https://attack.mitre.org/techniques/T1555/006/" |
| 192 | + |
| 193 | + |
| 194 | + |
| 195 | +[rule.threat.tactic] |
| 196 | +id = "TA0006" |
| 197 | +name = "Credential Access" |
| 198 | +reference = "https://attack.mitre.org/tactics/TA0006/" |
0 commit comments