From 244cdda427a177d6a99140008bb6ab55bbc64fec Mon Sep 17 00:00:00 2001 From: Samirbous <64742097+Samirbous@users.noreply.github.com> Date: Fri, 1 May 2026 17:35:19 +0100 Subject: [PATCH] [New] Multi-Cloud CLI Token and Credential Access Commands (#6012) * [New] Multi-Cloud CLI Token and Credential Access Commands Correlates process telemetry for shells and major cloud/Kubernetes CLIs when command lines match token or credential material access patterns (GCP, Azure, AWS, GitHub, kubectl, DigitalOcean, OCI). Flags hosts where multiple cloud targets appear occurs within five-minute window. * Update credential_access_multi_cloud_cli_token_harvesting.toml * Update credential_access_multi_cloud_cli_token_harvesting.toml * Update credential_access_multi_cloud_cli_token_harvesting.toml * Update credential_access_multi_cloud_cli_token_harvesting.toml * Apply suggestion from @eric-forte-elastic Co-authored-by: Eric Forte <119343520+eric-forte-elastic@users.noreply.github.com> * Update credential_access_multi_cloud_cli_token_harvesting.toml * Update credential_access_multi_cloud_cli_token_harvesting.toml * Update credential_access_multi_cloud_cli_token_harvesting.toml --------- Co-authored-by: Eric Forte <119343520+eric-forte-elastic@users.noreply.github.com> --- ...cess_multi_cloud_cli_token_harvesting.toml | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 rules/cross-platform/credential_access_multi_cloud_cli_token_harvesting.toml diff --git a/rules/cross-platform/credential_access_multi_cloud_cli_token_harvesting.toml b/rules/cross-platform/credential_access_multi_cloud_cli_token_harvesting.toml new file mode 100644 index 000000000..9e7564ac0 --- /dev/null +++ b/rules/cross-platform/credential_access_multi_cloud_cli_token_harvesting.toml @@ -0,0 +1,169 @@ +[metadata] +creation_date = "2026/04/29" +integration = ["endpoint", "windows", "system"] +maturity = "production" +updated_date = "2026/04/29" + +[rule] +author = ["Elastic"] +description = """ +Correlates process telemetry for shells and major cloud/Kubernetes CLIs when command lines match token or credential +material access patterns (GCP, Azure, AWS, GitHub, kubectl, DigitalOcean, OCI). Flags hosts where multiple cloud +targets appear within a five-minute window. +""" +false_positives = [ + """ + Automation, CI runners, and platform engineering scripts may legitimately print tokens or dump kubeconfig across + providers in one session. Baseline approved identities and runner images before tuning thresholds. + """, +] +from = "now-6m" +language = "esql" +license = "Elastic License v2" +name = "Multi-Cloud CLI Token and Credential Access Commands" +note = """## Triage and analysis + +### Investigating Multi-Cloud CLI Token and Credential Access Commands + +Each result row summarizes activity for one host, user, and five-minute time bucket. Review `Esql.process_command_line_values` for the +exact invocations and confirm whether the session was interactive, automated, or tied to a known pipeline. + +### Possible investigation steps + +- Map `Esql.cloud_targets` and `Esql.unique_clouds` to the underlying `process.command_line` values and parent + executables. +- Correlate with authentication, Kubernetes audit, and cloud API logs for misuse of printed tokens. +- Identify whether the parent chain indicates a remote shell, RMM, or scheduled task. + +### Response and remediation + +- If unauthorized, isolate the host, invalidate any printed material at the identity provider, and hunt for lateral + movement using the same time window as the alert. + +**GCP (gcloud / application-default credentials)** + +- Sign the user or build identity out of local gcloud sessions on the affected machine (example host session): + + `gcloud auth revoke --all` + +- Remove leaked Application Default Credentials on that host (often used by client libraries): + + `gcloud auth application-default revoke` + +- If a user OAuth refresh token or service account key was exposed, revoke or rotate it in Google Cloud Console (IAM + and admin: delete compromised keys; for end users, revoke OAuth tokens under Security or Workspace admin tools as + applicable). + +**Azure (`az` / `azd`)** + +- Clear cached CLI sessions on the host so new tokens are not silently reusable from disk: + + `az logout` + + `az account clear` + +- If `az account get-access-token`, `Get-AzAccessToken`, or `azd auth token` output was captured, treat the bearer as + compromised: rotate the underlying secret (for example app registration client secret or federated credential), + revoke sessions in Microsoft Entra ID where supported, and enforce re-authentication with Conditional Access. + +**GitHub (`gh` / PATs)** + +- Remove the GitHub CLI session from the affected profile: + + `gh auth logout` + +- If a personal access token or fine-grained token was printed, revoke it under GitHub user or organization settings + (Developer settings → Personal access tokens), and rotate any secrets or deploy keys that were readable with that + token. + +For all providers, prefer provider-console revocation and rotation when a token string left the trust boundary; local +`logout`/`revoke` alone does not invalidate tokens that were already copied off-host. +""" +references = [ + "https://attack.mitre.org/techniques/T1528/", + "https://attack.mitre.org/techniques/T1552/", +] +risk_score = 73 +rule_id = "2b9a3b7a-0891-4a89-abbe-dca753c403cd" +severity = "high" +tags = [ + "Domain: Endpoint", + "Domain: Cloud", + "OS: Windows", + "OS: Linux", + "OS: macOS", + "Use Case: Threat Detection", + "Tactic: Credential Access", + "Data Source: Elastic Defend", + "Data Source: Windows Security Event Logs", + "Data Source: Sysmon", + "Resources: Investigation Guide" +] +timestamp_override = "event.ingested" +type = "esql" +query = ''' +FROM logs-endpoint.events.process-*, logs-system.security-*, logs-windows.sysmon_operational-* METADATA _id, _index, _version +| WHERE event.category == "process" AND KQL(""" event.type : "start" and not event.action : "fork" """) + AND process.command_line IS NOT NULL + AND ( + TO_LOWER(process.name) IN ( + "cmd.exe", "powershell.exe", "pwsh.exe", + "sh", "bash", "zsh", "dash", "fish", "ksh", + "gcloud", "gcloud.cmd", "az", "az.cmd", "azd", "azd.exe", + "gh", "gh.exe", "aws", "aws.exe", + "kubectl", "kubectl.exe", + "doctl", "doctl.exe", + "oci", "oci.exe" + ) OR + TO_LOWER(process.parent.name) IN ( + "cmd.exe", "powershell.exe", "pwsh.exe", + "sh", "bash", "zsh", "dash", "fish", "ksh", "bun", "bun.exe", + "node", "node.exe", "java", "java.exe" + ) + ) + AND process.command_line RLIKE """.*(config-helper\s.*--format|auth\s+print-access-token|auth\s+print-identity-token|auth\s+application-default\s+print|get-access-token\s.*--output|Get-AzAccessToken|azd\s+auth\s+token|az\s+account\s+get-access-token|gh\s+auth\s+(token|status)|aws\s+sts\s+(get-session-token|get-caller-identity|assume-role)|aws\s+configure\s+(export-credentials|list)|kubectl\s+config\s+view\s.*--raw|kubectl\s+get\s+secret|doctl\s+auth\s+(list|init)|oci\s+session\s+authenticate|oci\s+iam\s.*token).*""" +| EVAL cloud_target = CASE( + process.command_line RLIKE ".*(gcloud|config-helper|print-access-token|print-identity-token).*", "GCP", + process.command_line RLIKE ".*(azd auth|az account|Get-AzAccessToken).*", "AZURE", + process.command_line RLIKE ".*(aws sts|aws configure).*", "AWS", + process.command_line RLIKE ".*(gh auth).*", "GITHUB", + process.command_line RLIKE ".*(kubectl config|kubectl get secret).*", "KUBERNETES", + process.command_line RLIKE ".*(doctl).*", "DIGITALOCEAN", + process.command_line RLIKE ".*(oci session|oci iam).*", "ORACLE" + ) +| WHERE cloud_target IS NOT NULL // drop unclassified events before aggregation +| STATS + Esql.cloud_targets = VALUES(cloud_target), + Esql.unique_clouds = COUNT_DISTINCT(cloud_target), + Esql.process_command_line_values = VALUES(process.command_line), + Esql.process_parent_executable_values = VALUES(process.parent.executable), + Esql.first_seen = MIN(@timestamp), + Esql.last_seen = MAX(@timestamp), + Esql.event_count = COUNT(*) + BY host.name, host.id, user.name +| WHERE Esql.unique_clouds >= 2 +| KEEP Esql.*, user.name, host.name, host.id +''' + +[[rule.threat]] +framework = "MITRE ATT&CK" + +[[rule.threat.technique]] +id = "T1528" +name = "Steal Application Access Token" +reference = "https://attack.mitre.org/techniques/T1528/" + +[[rule.threat.technique]] +id = "T1552" +name = "Unsecured Credentials" +reference = "https://attack.mitre.org/techniques/T1552/" + +[[rule.threat.technique.subtechnique]] +id = "T1552.001" +name = "Credentials In Files" +reference = "https://attack.mitre.org/techniques/T1552/001/" + +[rule.threat.tactic] +id = "TA0006" +name = "Credential Access" +reference = "https://attack.mitre.org/tactics/TA0006/"