Safeguard
Cloud Security

GCP IAM privilege escalation paths explained

Attackers escalate GCP privilege using IAM actAs chains, default Editor service accounts, and Cloud Build tokens -- no exploit code required.

Vikram Iyer
Cloud Security Engineer
7 min read

In December 2019, Rhino Security Labs published research cataloging more than 20 distinct ways a low-privileged Google Cloud IAM principal can escalate to Editor or Owner without ever touching an admin's stolen credential. The root cause isn't a software bug — it's how GCP resolves permissions across Compute Engine, Cloud Functions, Cloud Build, and Deployment Manager, each of which can execute code as a service account the caller merely has permission to "act as." A developer holding iam.serviceAccounts.actAs on a single over-privileged service account can, in minutes, attach that identity to a new VM, function, or build job and inherit its permissions wholesale. Because these misconfigurations don't trigger a CVE or a vulnerability scanner alert, they can sit in production GCP projects for years. This post walks through the specific permission combinations that create escalation paths, the default service account behavior that still ships in projects created before May 2019, and how security teams find these chains before an attacker does.

What counts as a GCP IAM privilege escalation path?

A GCP IAM privilege escalation path is any permission or role combination that lets a principal grant itself, or borrow, capabilities beyond what its assigned role intends — usually by impersonating a more privileged service account rather than exploiting a code flaw. Unlike a CVE, there's no patch to apply; the "vulnerability" is a policy decision baked into how a project's IAM bindings, service accounts, and resource-creation permissions line up. Rhino Security Labs' December 2019 write-up, "Privilege Escalation in Google Cloud Platform (Part 1: IAM)," enumerated over 20 such paths spanning at least nine GCP services, and Google's own security team has since shipped tooling (the IAM Recommender, generally available since 2020) specifically because these chains kept showing up in customer environments. Common permissions involved include iam.serviceAccounts.actAs, iam.serviceAccounts.getAccessToken, iam.serviceAccounts.signBlob, iam.serviceAccounts.implicitDelegation, deploymentmanager.deployments.create, cloudfunctions.functions.create, cloudbuild.builds.create, compute.instances.create, iam.roles.update, and resourcemanager.projects.setIamPolicy. None of these are exotic — most appear in predefined roles that get handed to CI pipelines, contractors, and junior engineers every day.

Which IAM permission is involved in the most escalation techniques?

iam.serviceAccounts.actAs is the single permission implicated in at least seven of the documented GCP escalation paths, because it's the permission GCP checks whenever a resource — a VM, a Cloud Function, a build, a Cloud Run job — is launched "as" a given service account. That permission is bundled into roles/iam.serviceAccountUser and roles/iam.serviceAccountTokenCreator, both of which are routinely granted at the project level so deployment pipelines can run under a service identity. Here's the concrete chain: a project has a CI service account, ci-deploy@project-id.iam.gserviceaccount.com, bound to roles/editor. A contractor is granted only roles/iam.serviceAccountUser on that one service account plus roles/compute.instanceAdmin.v1. That contractor creates a VM specifying ci-deploy@... as its attached identity, SSHes in, and runs curl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token. The response is a live OAuth token scoped to Editor — full read/write across almost every resource in the project — even though the contractor's own IAM role never mentioned Editor anywhere.

How does the default Compute Engine service account still enable escalation in production projects?

Projects created before May 3, 2019 — and any project since where an operator manually re-added the binding — still attach roles/editor at the project level to the default Compute Engine service account, formatted as PROJECT_NUMBER-compute@developer.gserviceaccount.com. Google stopped auto-granting Editor to that account for newly created projects starting that date, but the change wasn't retroactive, so audits run today still routinely find the legacy binding in place on projects that have existed for five, six, seven years. Because every new Compute Engine VM uses this default service account unless a different one is explicitly specified, the practical effect is that compute.instances.create permission alone — with no scope restriction on the instance — is enough to mint an Editor-scoped token from the metadata server, the same 169.254.169.254 endpoint used in the previous section. Tools like ScoutSuite and Google's own open-source gcp_scanner (released 2021) both include explicit checks for this exact binding because it remains one of the highest-frequency findings in real-world GCP environments.

Can a low-privileged user reach Owner through Cloud Build or Deployment Manager?

Yes — Cloud Build's default service account, PROJECT_NUMBER@cloudbuild.gserviceaccount.com, has historically held roles/editor, so anyone with cloudbuild.builds.create permission (bundled into roles/cloudbuild.builds.editor, a role commonly handed to any engineer who needs to trigger a pipeline) can submit a build config containing a step that calls gcloud projects add-iam-policy-binding against their own account, granting themselves roles/owner outright. A minimal cloudbuild.yaml step doing exactly this needs only the gcloud CLI, which ships in Google's standard build images by default. Deployment Manager offers a parallel path: a principal with deploymentmanager.deployments.create and iam.serviceAccounts.actAs on an Editor-bound service account can submit a deployment template whose output block extracts and exposes that service account's access token, achieving the same result without ever touching Cloud Build.

What does direct IAM policy binding abuse look like?

The most direct escalation path skips service account impersonation entirely: a principal holding resourcemanager.projects.setIamPolicy or iam.roles.update can simply rewrite the project's IAM policy to add themselves as roles/owner. setIamPolicy is granted by roles like roles/resourcemanager.projectIamAdmin, which teams often delegate to platform engineers who only need to manage service-to-service bindings, not grant themselves ownership. iam.roles.update is subtler: if an attacker already holds a custom role — even one scoped to a handful of narrow permissions — and also has update rights on that role's definition, they can edit the role in place to add resourcemanager.projects.setIamPolicy, then use it. Both paths require zero code execution and zero lateral movement; they're a single gcloud or API call once the permission is confirmed, which is why they show up first in every GCP red-team checklist, including Google's own internal gcp_scanner tool.

How can security teams detect these paths before attackers do?

Security teams detect escalation paths by continuously graphing effective IAM permissions against actual resource-creation reachability, not by reading role names off a dashboard, because a custom role bundling just three narrow permissions — say, iam.serviceAccounts.actAs, compute.instances.create, and compute.instances.setMetadata — can be exactly as dangerous as roles/editor while looking harmless in an access review. Google's IAM Recommender, generally available since 2020, flags unused permissions on existing bindings but doesn't model multi-step escalation chains across services, and point-in-time scans with tools like ScoutSuite or gcp_scanner catch known patterns only at the moment they're run. Effective detection means alerting specifically on the combination of an actAs grant plus any compute, function, build, or deployment create permission on the same principal, reviewing every service account still bound to roles/editor at the project level, and re-running that analysis on every IaC change rather than on a quarterly cadence — because a single Terraform PR can introduce a new escalation chain in seconds.

How Safeguard Helps

Safeguard's reachability analysis maps IAM permission graphs the same way it maps application call graphs, so instead of flagging every iam.serviceAccountUser grant in isolation, it traces whether that grant is actually chained to an actAs-able service account bound to roles/editor with a reachable compute, function, or build create permission — the difference between a theoretical finding and an exploitable path an attacker could walk today. Griffin AI, Safeguard's reasoning engine, correlates that IAM graph with your live workloads and CI/CD configuration to rank which chains are real and reachable versus which are dead ends, cutting hundreds of raw IAM warnings down to the handful that matter. Because cloud identity risk and software supply chain risk increasingly intersect — a compromised build dependency can pivot straight into an over-permissioned Cloud Build service account — Safeguard's SBOM generation and ingest pipeline ties the artifacts your builds produce back to the identities and permissions that built them. Where the fix is mechanical, such as replacing a broad actAs grant with a scoped custom role or removing a legacy roles/editor binding, Safeguard opens an auto-fix pull request against the Terraform or IaC source that defined it, so remediation ships through the same review process as any other code change.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.