Home Attacking Google Cloud — Part 4: Service Account Impersonation & IAM Privilege Escalation
Post
Cancel

Attacking Google Cloud — Part 4: Service Account Impersonation & IAM Privilege Escalation

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 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 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 (+ setIamPolicy to 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 yourself roles/owner outright. 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 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

This post is licensed under CC BY 4.0 by the author.
Zeynep Çelik

Cloud security researcher focused on offensive GCP and cloud tradecraft — identities, privilege escalation and persistence.

Attacking Google Cloud — Part 3: Who Am I? Recon, Enumeration & Credential Hunting

Attacking Google Cloud — Part 5: What a Single Leaked API Key Gives Away

Comments powered by Disqus.

Powered by 0xhav0c © 2022–2026 - Privacy Policy