Attacking Google Cloud — Part 4 of 7 · a series by Zeynep Çelik.
The heart of GCP offense. Four structural traits make privilege escalation more fluid here than in AWS — and the last of them is a bridge straight into Workspace and someone’s inbox.
Core chapter · assumes a low-priv foothold from Part 3 · every primitive maps to a real IAM permission you can grep for.
If you arrive from AWS, GCP’s identity model looks familiar enough to be dangerous. “Role / service account, permissions, resource” — same words, roughly the same shapes. But the mechanics underneath are different in ways that consistently favour the attacker. In AWS a role is temporary and fenced in by a trust policy; in GCP a service account can hand out a permanent key, be impersonated over the wire, be attached to almost anything, and even reach out of the cloud into Workspace. This part walks the four differences with real permissions, real API calls, and the detection that catches each.
Two concepts that resemble a “role,” diverging at three critical points
Trait one — persistent, downloadable credentials
In AWS you can’t mint a permanent API key for a role; you assume it and get temporary STS credentials. In GCP you can create a real, non-expiring JSON key for a service account. That turns an identity into a file — and files leak. Because the key has a fixed JSON structure, hunting for it on a compromised host or in a git history is extremely productive, and one hit usually means you’ve captured a whole principal outright. ATT&CK T1098.001 · T1552.001
1
2
3
gcloud iam service-accounts keys create k.json --iam-account=SA@PROJECT.iam.gserviceaccount.com
gcloud auth activate-service-account --key-file=k.json
gcloud auth print-access-token
Trait two — access scope only limits stolen tokens
Here’s the nuance that trips people up. A token you pull from a GCE VM’s metadata is capped by that VM’s access scope, a legacy OAuth limit that sits on top of IAM. So even if the bound service account is org-owner, the metadata token might only be allowed to read storage. That feels like a wall — until you realise it only applies to the stolen token. If you hold a real key or impersonation rights, you mint a brand-new token with any scope you like, and the wall simply isn’t there.
Why a real key is worth far more than a metadata token
1
gcloud auth print-access-token --scopes=https://www.googleapis.com/auth/cloud-platform
Trait three — actAs: attach any SA to any resource
In AWS, a role can only be attached to the services its trust policy allows, which fences in your escalation. GCP has no such per-service trust wall. If you can create a resource — a VM, a function, a Cloud Run service, a build — and you hold iam.serviceAccounts.actAs (usually via roles/iam.serviceAccountUser), you can attach the most privileged service account available and then simply read its token from inside. These are the workhorse escalation primitives you’ll reach for again and again:
🔑 Key escalation primitives
compute.instances.create+ actAs → spin up a VM with the editor default SA, read the token from metadata.cloudfunctions.functions.create/run.services.create+ actAs (+setIamPolicyto make it public) → run code as the chosen SA.cloudbuild.builds.create→ Cloud Build’s default SA is classically editor; print its token inside a build step and exfiltrate it.deploymentmanager.deployments.create→ push IAM bindings through the Deployment Manager Google SA.resourcemanager.projects.setIamPolicy→ grant yourselfroles/owneroutright. Game over.iam.serviceAccountKeys.create/iam.serviceAccounts.getAccessToken/signJwt→ mint credentials or tokens for another SA directly.
Mapping this by hand across a large org is tedious and error-prone; the community tool PurplePanda (HackTricks) builds the escalation graph for you, and RhinoSecurityLabs published a well-known set of GCP privesc scripts that automate several of the primitives above. ATT&CK T1098.003 · T1548
Trait four — impersonation and chaining
You don’t even need to find a key. With roles/iam.serviceAccountTokenCreator you can mint a token as another service account directly through the IAM Credentials API. And because impersonation composes, a chain of individually harmless grants — A can impersonate B, B can impersonate C — collapses into a single ladder that carries an ordinary account up to owner. The API’s delegates field lets you traverse the whole chain in one call.
graph LR
U["Low-priv identity"] -->|tokenCreator| B["service-account B"]
B -->|tokenCreator| C["service-account C"]
C -->|"actAs / setIamPolicy"| O["roles/owner — game over"]
1
2
3
4
5
gcloud auth print-access-token --impersonate-service-account=TARGET@PROJECT.iam.gserviceaccount.com
# raw API — generateAccessToken (siblings: signJwt, generateIdToken)
curl -s -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" -d '{"scope":["https://www.googleapis.com/auth/cloud-platform"]}' \
"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/TARGET@PROJECT.iam.gserviceaccount.com:generateAccessToken"
Individually harmless tokenCreator grants become a ladder to owner
The GCP→Workspace bridge: Domain-Wide Delegation
Now the one that turns a cloud finding into a company-wide one. If a service account has been granted domain-wide delegation on the Workspace side, it can impersonate Workspace users — not just other service accounts. Using signJwt, you forge a signed assertion on behalf of a chosen user for a scope like Gmail, Drive, or the Admin SDK, and exchange it for a token. That’s a straight line from a compromised GCP project into the CEO’s inbox. Checking every service account for DWD should be a fixed item on your list; in more than one engagement it’s the single highest-impact finding. ATT&CK T1550.001 · T1078.004
🔑 Definition — the three over-privileged default SAs
Created automatically and classically granted editor, which makes them recurring escalation targets:
PROJECT_NUMBER-compute@developer.gserviceaccount.com(Compute Engine)PROJECT_ID@appspot.gserviceaccount.com(App Engine / Cloud Functions)PROJECT_NUMBER@cloudbuild.gserviceaccount.com(Cloud Build)If you can run code as any of them, you likely have editor on the project.
🔑 Detection
Watch these audit-log signals for impersonation, key creation, and dangerous IAM changes:
1
2
3
4
protoPayload.serviceName="iamcredentials.googleapis.com" AND protoPayload.methodName:"GenerateAccessToken"
OR protoPayload.methodName="google.iam.admin.v1.CreateServiceAccountKey"
OR (protoPayload.methodName="SetIamPolicy" AND
protoPayload.serviceData.policyDelta.bindingDeltas.role:("tokenCreator" OR "owner" OR "serviceAccountUser"))
🔑 Prevention
Disable service-account key creation via org policy and move to Workload Identity Federation; continuously inventory
tokenCreator/actAs/ owner bindings with Cloud Asset Inventory; strip the editor role from the default compute, appspot, and cloudbuild SAs; and grant domain-wide delegation only where it is genuinely unavoidable.
Next in the series → Part 5 · What a Single Leaked API Key Gives Away
Comments powered by Disqus.