
GCP Security Assessment and Enumeration
GCP security assessment methodology covering project enumeration, service account discovery, storage bucket exposure, and metadata API exploitation.
Introduction
GCP's security model revolves around projects, service accounts, and IAM bindings rather than the policy-document approach used by AWS. Every API call in GCP is governed by Cloud IAM, and the blast radius of a compromised identity depends entirely on what roles are bound to it across which projects. Service accounts are the primary target — they can be bound to GCE instances, impersonated by other accounts, and can have JSON key files exported and used from anywhere on the internet.
The GCP metadata service at http://metadata.google.internal/computeMetadata/v1/ is the fastest pivot point from a compromised GCE instance. It exposes the bound service account, its access token, and full project details with a single HTTP request. Unlike AWS IMDSv2, GCP's metadata endpoint does not require session-oriented token exchange — the Metadata-Flavor: Google header is the only protection, making SSRF exploitation trivial.
Initial access context dictates the starting methodology. From a compromised GCE instance you pull tokens directly from the metadata API. With a stolen service account JSON key, you authenticate with gcloud auth activate-service-account. With an OAuth token obtained via phishing or token theft, you configure it directly via gcloud config set auth/access_token_file. Each path lands in the same enumeration workflow once you have working credentials.
Authorization Required
GCP assessments must have explicit written authorization covering the specific project IDs and services in scope. Cloud audit logs record every API call with caller IP and identity. Unauthorized enumeration against GCP projects violates Google's Terms of Service and triggers automated abuse detection.
Impact
- Service account with
roles/ownerorroles/editoron a project grants full control over all resources including billing, compute, and storage - Publicly readable Cloud Storage buckets expose data with no authentication and leave no trace in access logs unless Data Access audit logging is explicitly enabled
- Service account JSON keys persist indefinitely unless rotated — a leaked key file is a permanent backdoor until revoked
- Workload Identity Federation misconfigurations allow external identities (GitHub Actions, AWS roles) to impersonate GCP service accounts
- Organization-level IAM policy mistakes propagate to every project under the org
- Cloud Functions and Cloud Run services running with overprivileged service accounts allow privilege escalation through code execution
- Default compute service account has
roles/editoron the project in older GCP environments — GCE instances inherit this by default
Technical Details
GCP's IAM hierarchy flows from Organization → Folder → Project → Resource. Roles bound at a higher level inherit downward. The resourcemanager.projects.list permission lets you enumerate all projects visible to your identity, which is the first step in mapping the attack surface.
Service accounts exist in a specific project but can be granted roles in other projects. The IAM policy for a project (getIamPolicy) returns every principal with bindings, including external service accounts from other projects. Cross-project service account usage is a common lateral movement path.
Cloud Storage ACLs operate at two levels: the bucket ACL (legacy) and IAM policies. Uniform bucket-level access (UBL) disables per-object ACLs and centralizes control in IAM. When UBL is disabled, individual objects can be publicly accessible even if the bucket policy appears restricted.
The metadata endpoint returns short-lived access tokens (1-hour expiry) tied to the instance's bound service account. The token scope is determined by the access scopes configured at instance creation — cloud-platform scope gives the token access to all GCP APIs limited only by the service account's IAM roles.
Identify Initial Access Context
Determine the access vector before starting enumeration.
# With gcloud configured
gcloud auth list
gcloud config list
# With OAuth token (e.g., from SSRF or token theft)
curl -H "Authorization: Bearer $TOKEN" \
https://www.googleapis.com/oauth2/v1/tokeninfo
# Activate service account from JSON key
gcloud auth activate-service-account \
--key-file=/path/to/service-account-key.json
# Set project context
gcloud config set project <PROJECT_ID>Pull Credentials from GCE Metadata Service
From a compromised GCE instance, the metadata API gives you the bound service account identity and a live access token.
BASE="http://metadata.google.internal/computeMetadata/v1"
HDR='-H "Metadata-Flavor: Google"'
# Project info
curl -s $BASE/project/project-id $HDR
curl -s $BASE/project/numeric-project-id $HDR
# Instance info
curl -s $BASE/instance/name $HDR
curl -s $BASE/instance/zone $HDR
curl -s $BASE/instance/service-accounts/ $HDR
# Service account details
curl -s $BASE/instance/service-accounts/default/email $HDR
curl -s $BASE/instance/service-accounts/default/scopes $HDR
# Pull access token (expires in ~1 hour)
curl -s $BASE/instance/service-accounts/default/token $HDR
# Returns: {"access_token":"ya29.xxx","expires_in":3599,"token_type":"Bearer"}
# SSH keys and custom metadata
curl -s $BASE/instance/attributes/ $HDR
curl -s $BASE/instance/attributes/ssh-keys $HDRExport the token and use it directly for API calls:
TOKEN=$(curl -s http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token \
-H "Metadata-Flavor: Google" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
curl -H "Authorization: Bearer $TOKEN" \
"https://cloudresourcemanager.googleapis.com/v1/projects"Enumerate Projects and Organization
Map the full scope of accessible resources.
# List all accessible projects
gcloud projects list --format="table(projectId,name,projectNumber,lifecycleState)"
# Organization discovery
gcloud organizations list
# Folders (requires resourcemanager.folders.list)
gcloud resource-manager folders list --organization=<ORG_ID>
# Project ancestors (full hierarchy)
gcloud projects get-ancestors <PROJECT_ID>IAM Policy Enumeration
IAM policies reveal every identity with access and what they can do.
# Project-level IAM policy
gcloud projects get-iam-policy <PROJECT_ID> \
--format=json > project-iam-policy.json
# Organization-level policy (high value if accessible)
gcloud organizations get-iam-policy <ORG_ID> \
--format=json > org-iam-policy.json
# List all service accounts in project
gcloud iam service-accounts list \
--project=<PROJECT_ID> \
--format="table(email,displayName,disabled)"
# Service account IAM policy (who can act as this SA)
gcloud iam service-accounts get-iam-policy <SA_EMAIL> \
--format=json
# Check for SA keys (key material exported externally)
gcloud iam service-accounts keys list \
--iam-account=<SA_EMAIL> \
--filter="keyType=USER_MANAGED"Parse the policy for overprivileged bindings:
# Find all bindings with primitive roles (owner/editor/viewer)
gcloud projects get-iam-policy <PROJECT_ID> \
--format=json | \
python3 -c "
import sys, json
p = json.load(sys.stdin)
for b in p.get('bindings', []):
role = b['role']
if 'owner' in role or 'editor' in role or 'admin' in role:
print(f'{role}:')
for m in b['members']:
print(f' {m}')
"
# Find allUsers / allAuthenticatedUsers bindings (public access)
gcloud projects get-iam-policy <PROJECT_ID> \
--format=json | \
grep -E "allUsers|allAuthenticatedUsers"Enumerate Compute Resources
# All instances across all zones
gcloud compute instances list --format="table(name,zone,status,serviceAccounts.email)"
# Instance details including startup scripts (may contain secrets)
gcloud compute instances describe <INSTANCE_NAME> \
--zone=<ZONE> \
--format=json
# Firewall rules — look for overly permissive ingress
gcloud compute firewall-rules list \
--format="table(name,direction,priority,sourceRanges,targetTags,allowed)"
# VPC networks
gcloud compute networks list
# Check for instances with public IPs and broad SSH access
gcloud compute firewall-rules list \
--filter="direction=INGRESS AND allowed.ports:22 AND sourceRanges:0.0.0.0/0"
# Cloud Run services (often publicly accessible)
gcloud run services list --platform=managed
# Cloud Functions
gcloud functions list
gcloud functions describe <FUNCTION_NAME> --format=jsonCloud Storage Bucket Enumeration and ACL Audit
# List all buckets in project
gsutil ls -p <PROJECT_ID>
# Alternative with gcloud
gcloud storage buckets list --project=<PROJECT_ID> \
--format="table(name,location,storageClass,iamConfiguration.uniformBucketLevelAccess.enabled)"
# Check bucket ACL (legacy ACL, only relevant if UBL disabled)
gsutil acl get gs://<BUCKET_NAME>
# Check IAM policy on bucket
gsutil iam get gs://<BUCKET_NAME>
# Check if bucket allows public access
gsutil iam get gs://<BUCKET_NAME> | grep -E "allUsers|allAuthenticatedUsers"
# List bucket contents (if readable)
gsutil ls -la gs://<BUCKET_NAME>
# Recursive list (careful with large buckets)
gsutil ls -r gs://<BUCKET_NAME>/**
# Check object ACLs on specific file (when UBL is disabled)
gsutil acl get gs://<BUCKET_NAME>/<OBJECT_KEY>
# Download interesting files
gsutil cp gs://<BUCKET_NAME>/credentials.json ./
gsutil cp gs://<BUCKET_NAME>/terraform.tfstate ./Check for public access across all project buckets:
for bucket in $(gsutil ls -p <PROJECT_ID>); do
echo "=== $bucket ==="
gsutil iam get $bucket 2>/dev/null | grep -E "allUsers|allAuthenticatedUsers" \
&& echo " [!] PUBLIC ACCESS FOUND" || echo " [ok] Not public"
doneEnumerate Enabled Services and APIs
The enabled services list reveals the attack surface — BigQuery data, Kubernetes clusters, Secret Manager entries.
# All enabled services
gcloud services list --enabled \
--format="table(config.name,config.title)"
# Kubernetes clusters
gcloud container clusters list \
--format="table(name,location,status,currentMasterVersion)"
# Cloud SQL instances
gcloud sql instances list
# Cloud Spanner
gcloud spanner instances list
# Secret Manager secrets (names only — need secretmanager.versions.access for values)
gcloud secrets list --format="table(name,createTime,replication)"
# Access a secret version (requires secretmanager.versions.access)
gcloud secrets versions access latest --secret=<SECRET_NAME>
# BigQuery datasets
bq ls --project_id=<PROJECT_ID>
# Pub/Sub topics and subscriptions
gcloud pubsub topics list
gcloud pubsub subscriptions listAttack Tools
ScoutSuite automates multi-service configuration assessment and generates an HTML report with findings categorized by severity.
# Install
pip install scoutsuite
# Scan with application default credentials
scout gcp --user-account
# Scan with specific project
scout gcp --user-account --project-id <PROJECT_ID>
# Scan with service account key
scout gcp --service-account /path/to/key.json
# Generate report to specific directory
scout gcp --user-account --report-dir ./scoutsuite-report
# Scan specific services only
scout gcp --user-account --services iam,gcs,compute,cloudsql
# Skip specific services
scout gcp --user-account --skip cloudfunctions,cloudbuildScoutSuite checks for: public storage buckets, overprivileged service accounts, weak firewall rules, unencrypted resources, audit logging disabled, MFA enforcement, and OS Login configuration.
# ScoutSuite also outputs raw JSON
python3 -c "
import json
with open('scoutsuite-results/scoutsuite_results_gcp-<project>.js') as f:
# Strip the leading 'scoutsuite_results =' assignment
data = json.loads(f.read().split('=',1)[1].strip().rstrip(';'))
services = data.get('services', {})
for svc, svc_data in services.items():
findings = svc_data.get('findings', {})
for finding_id, finding in findings.items():
if finding.get('flagged_items', 0) > 0:
print(f'[{svc}] {finding[\"description\"]}: {finding[\"flagged_items\"]} items')
"enumerate-iam.py brute-forces which IAM permissions your current identity holds by attempting API calls across all GCP services. Useful for understanding the true capability of a compromised token.
# Clone and install
git clone https://github.com/aquasecurity/enumerate-iam-gcp
pip install -r requirements.txt
# Run against current gcloud credentials
python3 enumerate_iam.py \
--project-id <PROJECT_ID>
# Run with explicit access token
python3 enumerate_iam.py \
--project-id <PROJECT_ID> \
--access-token ya29.xxxxx
# Run against specific service account
python3 enumerate_iam.py \
--project-id <PROJECT_ID> \
--service-account-key /path/to/key.json
# Output to file
python3 enumerate_iam.py \
--project-id <PROJECT_ID> \
--output-file permissions.jsonThe tool makes ~500+ API calls to test permissions. Expect this to generate significant audit log volume. Key permissions to look for:
| Permission | Impact |
|---|---|
iam.serviceAccounts.actAs | Impersonate any SA in project |
iam.serviceAccountKeys.create | Generate persistent SA keys |
iam.roles.update | Escalate privileges via role modification |
resourcemanager.projects.setIamPolicy | Grant arbitrary IAM roles |
compute.instances.setMetadata | Inject SSH keys or startup scripts |
secretmanager.versions.access | Read secrets |
storage.objects.get | Read bucket contents |
Native gcloud enumeration with specific flags useful during assessments.
# Identify the current authenticated identity
gcloud auth list
gcloud config get-value account
gcloud config get-value project
# Test specific permission (policy simulator — requires own permissions)
gcloud policy-intelligence simulate-access \
--resource=//cloudresourcemanager.googleapis.com/projects/<PROJECT_ID> \
--principal=serviceAccount:<SA_EMAIL> \
--permission=compute.instances.list
# Check testable permissions for a resource
gcloud iam list-testable-permissions \
//cloudresourcemanager.googleapis.com/projects/<PROJECT_ID> \
--format="table(name,stage)"
# Find all custom roles defined in project
gcloud iam roles list --project=<PROJECT_ID>
# Inspect a custom role's permissions
gcloud iam roles describe <ROLE_ID> --project=<PROJECT_ID>
# Audit organization policies (constraints)
gcloud resource-manager org-policies list \
--project=<PROJECT_ID>
# Check if domain restriction constraint is set (prevents external accounts)
gcloud resource-manager org-policies describe \
constraints/iam.allowedPolicyMemberDomains \
--project=<PROJECT_ID>
# Cloud Logging — check if data access logs are enabled
gcloud projects get-iam-policy <PROJECT_ID> \
--format=json | python3 -c "
import sys, json
p = json.load(sys.stdin)
for ac in p.get('auditConfigs', []):
print(ac['service'])
for lc in ac.get('auditLogConfigs', []):
print(f' {lc[\"logType\"]}')
"GCPBucketBrute enumerates GCS bucket names and checks permissions without authentication.
git clone https://github.com/RhinoSecurityLabs/GCPBucketBrute
pip install -r requirements.txt
# Enumerate using keyword wordlist
python3 gcpbucketbrute.py \
-k company-name \
-o results.txt
# With custom wordlist
python3 gcpbucketbrute.py \
-k company-name \
-w /usr/share/wordlists/bucket-names.txt \
-o results.txt
# With authenticated session (finds buckets you may not have unauthenticated access to)
python3 gcpbucketbrute.py \
-k company-name \
--auth
# Checks performed per bucket:
# - Bucket exists (HTTP 200 vs 404/403)
# - Public listable (allUsers storage.buckets.list)
# - Public readable objects (allUsers storage.objects.get)
# - Authenticated read/write/admin (allAuthenticatedUsers)Naming patterns to target:
company-name
company-name-backup
company-name-prod
company-name-dev
company-name-staging
company-name-logs
company-name-assets
company-name-media
company-name-tf-state
company-name-terraformDetection
GCP Cloud Audit Logs are the primary detection data source. There are three log types: Admin Activity (always on, no charge), Data Access (disabled by default, must be explicitly enabled per service), and System Event (automatic).
Critical gap: If Data Access audit logging is not enabled, reads from Cloud Storage, Secret Manager, and other data services are invisible in logs. Attackers reading bucket contents or secrets leave no trace unless this is configured.
# Admin Activity logs — always enabled
logName="projects/<PROJECT>/logs/cloudaudit.googleapis.com%2Factivity"
# Data Access logs — must be enabled
logName="projects/<PROJECT>/logs/cloudaudit.googleapis.com%2Fdata_access"
# System events
logName="projects/<PROJECT>/logs/cloudaudit.googleapis.com%2Fsystem_event"Key event types to alert on:
| Event | Log Source | Indicator |
|---|---|---|
GetIamPolicy from unexpected IP | Data Access | IAM policy dumping |
ListBuckets + ListObjects in sequence | Data Access (GCS) | Storage enumeration |
serviceAccounts.keys.create | Admin Activity | SA key exfiltration |
SetIamPolicy granting primitive roles | Admin Activity | Privilege escalation |
instances.setMetadata | Admin Activity | SSH key injection |
secrets.versions.access | Data Access | Secret exfiltration |
GetAccessToken from non-GCE source | System | Token theft/replay |
SIEM detection queries (Log Analytics / Chronicle):
-- Service account key creation — user-managed keys are persistent backdoors
SELECT
timestamp,
protoPayload.authenticationInfo.principalEmail AS actor,
protoPayload.resourceName AS service_account,
protoPayload.requestMetadata.callerIp AS source_ip
FROM
`<PROJECT>.cloudaudit_googleapis_com_activity`
WHERE
protoPayload.methodName = "google.iam.admin.v1.CreateServiceAccountKey"
AND protoPayload.request.privateKeyType = "TYPE_GOOGLE_CREDENTIALS_FILE"
ORDER BY timestamp DESC-- Alert when allUsers or allAuthenticatedUsers added to any IAM policy
SELECT
timestamp,
protoPayload.authenticationInfo.principalEmail AS actor,
protoPayload.resourceName AS resource,
protoPayload.request AS request_detail
FROM
`<PROJECT>.cloudaudit_googleapis_com_activity`
WHERE
protoPayload.methodName IN ("SetIamPolicy", "google.iam.v1.IAMPolicy.SetIamPolicy")
AND (
TO_JSON_STRING(protoPayload.request) LIKE '%allUsers%'
OR TO_JSON_STRING(protoPayload.request) LIKE '%allAuthenticatedUsers%'
)-- Metadata queries from non-GCE IPs (potential SSRF exploitation)
SELECT
timestamp,
protoPayload.authenticationInfo.principalEmail,
protoPayload.requestMetadata.callerIp,
protoPayload.methodName
FROM
`<PROJECT>.cloudaudit_googleapis_com_data_access`
WHERE
protoPayload.requestMetadata.callerIp NOT LIKE "10.%"
AND protoPayload.requestMetadata.callerIp NOT LIKE "172.16.%"
AND protoPayload.requestMetadata.callerIp NOT LIKE "192.168.%"
AND protoPayload.serviceName = "iamcredentials.googleapis.com"
AND protoPayload.methodName = "GenerateAccessToken"Behavioral indicators by attacker privilege level:
- Viewer role only: High-volume
Get*andList*calls across multiple services in short succession — characteristic of automated enumeration - Editor role:
serviceAccounts.keys.createwithout corresponding deployment activity;instances.setMetadatawith new SSH keys - Owner /
setIamPolicy: Role binding changes adding external accounts or primitive roles
Remediation
IAM hardening:
- Eliminate
roles/ownerandroles/editorbindings; replace with least-privilege custom roles or predefined roles scoped to specific resources - Enforce
constraints/iam.allowedPolicyMemberDomainsorg policy to prevent external account bindings - Disable service account key creation org-wide with
constraints/iam.disableServiceAccountKeyCreationunless operationally required - Enable
constraints/iam.disableServiceAccountKeyUploadto prevent importing external keys - Audit
iam.serviceAccounts.actAsbindings — this permission allows full impersonation of the target SA - Require Workload Identity Federation instead of exported JSON keys for non-GCP workloads
Compute hardening:
- Do not use the default compute service account; create dedicated SAs with minimal permissions
- Disable access scopes that grant broad API access (
cloud-platformscope); specify only required scopes - Enable OS Login (
enable-oslogin=truemetadata) to use Cloud IAM for SSH instead of metadata-based keys - Enable Shielded VMs and Confidential Computing for sensitive workloads
- Block project-wide SSH key inheritance at instance level for sensitive instances
Storage hardening:
- Enable Uniform Bucket-Level Access (UBL) on all buckets — eliminates per-object ACL complexity
- Enable Public Access Prevention at org or bucket level
- Audit for
allUsersandallAuthenticatedUsersIAM bindings regularly - Enable Data Access audit logging for Cloud Storage in all production projects
- Use VPC Service Controls to restrict storage access to specific VPC networks
Audit logging:
- Enable Data Access audit logs for: Cloud Storage, Secret Manager, BigQuery, IAM, Cloud KMS
- Export logs to a separate project with restricted access to prevent tampering
- Set log retention to minimum 400 days for compliance
- Alert on
SetIamPolicy,CreateServiceAccountKey, andAccessSecretVersionevents
Secret management:
- Migrate all secrets from environment variables, startup scripts, and instance metadata to Secret Manager
- Rotate all service account keys; move to keyless auth where possible
- Scan GCS buckets and instance metadata for embedded credentials as part of regular audits
References
MITRE ATT&CK Techniques
- T1526 - Cloud Service Discovery
- T1087.004 - Account Discovery: Cloud Account
- T1530 - Data from Cloud Storage Object
Tools Documentation
- ScoutSuite - Multi-Cloud Security Auditing Tool
- enumerate-iam-gcp
- GCPBucketBrute
- gcloud CLI Reference
- GCP Metadata Server Documentation
- Cloud Audit Logs Documentation
- GCP IAM Roles Reference
- Workload Identity Federation
Next Steps
GCP IAM Privilege Escalation Techniques
GCP IAM privilege escalation paths including service account impersonation, binding escalation, and workload identity abuse.
Container Security
Container security including Docker escape techniques, containerization fundamentals, and hardening best practices for penetration testing and security assessments.