
Kit CTF OWASP autoalojado: una máquina, una organización gratuita de GitHub, sin dependencias de la nube
Un plano de control autoalojado para eventos de aprendizaje en seguridad — una máquina, una organización gratuita de GitHub.
Úsalo para una universidad, un instituto, un capítulo de OWASP, un meetup.
Lee AGENTS.md antes de escribir código. Es el manual de
operaciones: los comandos exactos que ejecuta CI, los modos de fallo que este
repositorio ya ha encontrado y las invariantes de revisión en
docs/reviewing.md. CLAUDE.md es un puntero al
mismo archivo.
Un cambio está listo cuando CI está en verde y cada hilo accionable de CodeRabbit en el último commit está resuelto (o rechazado y registrado). Los commits siguen Conventional Commits y no llevan atribución de IA.
El trabajo pequeño y bien especificado está etiquetado como
good first issue.
Los módulos nuevos empiezan como un issue, no como un PR — consulta
CONTRIBUTING.md.
Un plano de control, no un solo juego. La máquina le da a un evento su columna vertebral compartida — una organización de GitHub, el registro de equipos, una clasificación en vivo, un panel de administración para organizadores y la canalización de puntuación que lo alimenta. Los módulos conectan contenido de retos a esa columna vertebral, y cualquier subconjunto puede ejecutarse solo o junto con otros: Desarrollo Seguro de parche-a-puntuación, un banco de Quiz, un tablero de Jeopardy y retos de IA alojados externamente. El contrato de módulos es la frontera entre la columna vertebral y el contenido, así que la máquina está construida para alojar más módulos — forense, seguridad de API, nube — a medida que lleguen.
Por qué existe. El módulo de Desarrollo Seguro enseña defensa en lugar de ataque, y es una forma genuinamente buena de enseñar codificación segura. Hasta ahora, ejecutar uno significaba levantar Vercel, Upstash, Lambda y DynamoDB, asumir la factura de la nube y tener acceso a una imagen de puntuación privada. Eso es una petición razonable para una conferencia con presupuesto. Es una petición irrazonable para un curso universitario de seguridad, un club de instituto, una noche de capítulo de OWASP o un taller de fin de semana.
Este kit lo elimina. Todo se ejecuta desde Docker Compose en una máquina que ya tienes — un portátil, un sobremesa de repuesto, un VPS pequeño — más una organización gratuita de GitHub para los forks. Las rúbricas de los seis objetivos vienen dentro de la máquina, así que no hay imagen privada que solicitar ni código de puntuación que escribir. Nada se factura, nada llama a casa, y cuando el evento termina archivas los repos y detienes el stack.
Para quién es: cualquiera que quiera ejecutar este evento y no quiera convertirse en operador de nube para hacerlo — instructores de cursos, organizadores de clubes, líderes de capítulos de OWASP, facilitadores de talleres, equipos de seguridad que organizan un día de formación interno.
Desplegado y ejercitado de principio a fin; aún no ejecutado para una
cohorte real. La ruta de puntuación completa viene en el kit — el
POST /score con autenticación bearer del scorer, el flujo de trabajo de
puntuación autocontenido para los forks, el transporte de sondeo — y
scripts/smoke.sh impulsa toda esa canalización contra mocks. Más allá de
eso, el kit se ejecuta de forma continua en una máquina alojada desde el mismo
archivo Compose que este repositorio distribuye, GET /health informa la
revisión exacta que lo sirve, y una pasada de extremo a extremo sobre esa
instancia en vivo es donde se encontraron y corrigieron un montón de defectos
reales — de los que una suite con mocks no puede ver.
Lo que no ha ocurrido es un evento real: una cohorte de concursantes abriendo PRs reales contra forks reales, a la vez, durante horas. Esa es la brecha entre "la canalización funciona" y "la canalización funciona con 40 personas". Dos salvedades están abiertas en lugar de enterradas: el comparador de resultados de Security Shepherd tiene un límite residual declarado (una negativa con un fraseo inusual aún puede leerse como una resolución — puede infravalorar un parche correcto, nunca otorgar un punto gratis), y el perfil de carga de una cohorte completa no está probado. Detalle y estado actual: Estado y dependencias upstream.
Lo que hace que esos no hacen: formación en defensa de parche-a-puntuación calificada a través de pull requests de GitHub, un contrato de módulos para mezclar tipos de juego en una misma clasificación, y un plano de control que posees de principio a fin — una máquina, una organización gratuita, sin factura de nube, sin telemetría.
Este proyecto no está afiliado ni respaldado por la OWASP Foundation. Cuatro de los seis objetivos vulnerables son proyectos de OWASP (Juice Shop, WebGoat, Security Shepherd, VulnerableApp); DVWA y VAmPI son proyectos de la comunidad.
Vélo funcionando en dos minutos — sin organización de GitHub, sin app
OAuth, nada que configurar. Necesitas Docker con Compose v2 y openssl:```sh
git clone https://github.com/OWASP/owasp-ctf-in-a-box
cd owasp-ctf-in-a-box
./scripts/dev-stack up
Escribe secretos locales desechables, construye las imágenes del scorer y de la app, levanta el stack, siembra una tabla de clasificación de demostración a través de la API de puntuación real del scorer e imprime la URL para abrir. Deberías ver la tabla de clasificación con equipos sembrados y un gráfico de puntuación a lo largo del tiempo; `./scripts/dev-stack score <login> juice-shop 3` registra tres solves más en vivo. `./scripts/dev-stack down` lo desmonta.
**Ejecuta un evento real** con el asistente guiado. Añade la **[CLI de `gh`](https://cli.github.com)** (autenticada), más **una organización gratuita de GitHub** si el evento ejecuta Secure Development; `./setup/ctf-setup.sh check` verifica primero las herramientas:```sh
./setup/ctf-setup.sh # guided, prompts for values, resumable
Pide cada valor a medida que avanza — la URL de tu box, la org del evento, los
logins de admin, si ejecutas Secure Development, las credenciales de GitHub —
escribe .env, realiza
cada paso automatizable, te guía por los que son solo de la UI de GitHub y se
reanuda si te detienes y vuelves. Todo lo demás (el nombre del evento, qué
módulos se ejecutan, qué targets) es una configuración en tiempo de ejecución
en /admin, así que no hay ningún archivo de configuración que editar. Solo
pregunta lo que realmente necesitas: un evento sin Secure
Development no necesita org, ni forks, ni imagen de scorer, y nunca se le
pregunta por ellos. Previsualiza cualquier paso que modifique con --dry-run — narra los pasos
4–9 desde un .env que ya está completo, y se niega (por diseño) cuando
no hay login de admin, o cuando Secure Development está activado sin org. El
asistente cierra ejecutando ./setup/ctf-setup.sh doctor — una matriz de estado por fork que puedes volver a ejecutar en cualquier momento — y luego ofrece un deploy en fly.io opcional (por defecto no), de modo que poner el mismo evento en un nombre de host público es un flujo guiado — el nombre de host, un deploy previsualizado, luego una confirmación — en lugar de un recorrido por los documentos de deploy.
¿Quieres los detalles? Cada subcomando discreto, cada paso solo de UI y en qué
se diferencian las dos apps de GitHub:
docs/hosting.md.
¿Mejor en una nube? docs/aws.md (Terraform: ECS Fargate,
ElastiCache y un ALB — apply para levantar / destroy para bajar) o
docs/fly.md (una máquina Fly).
Secure Development — haz fork de una app deliberadamente vulnerable, encuentra el fallo, parchealo, abre un PR. Una GitHub Action en el fork ejecuta la rúbrica del target contra el parche y la puntuación llega al leaderboard (~30 s después en modo poll). Seis targets, 321 retos; el código original puntúa 0, un parche correcto gana sus puntos — con puerta en ambas direcciones. Necesita la org de GitHub y el pipeline de puntuación.
Quiz — preguntas de seguridad de selección única y múltiple, calificadas en la app
en el momento en que se responden (todo o nada en selección múltiple), con un límite de intentos
y un cooldown de reintento. Se crean desde /admin de una en una o se importan y
exportan como un único bundle JSON. No necesita GitHub, ni forks, ni pipeline.
Jeopardy — un tablero de flags creadas por los organizadores en
categorías. Los envíos se recortan y normalizan, se perdona el uso de mayúsculas y minúsculas a menos que una
flag esté marcada como sensible a mayúsculas (su tarjeta lo indica), con un cooldown de envío
y pistas de pago opcionales. La misma creación desde /admin + bundle JSON que el quiz.
Tampoco necesita GitHub.
AI — retos de prompt-injection y guardrails alojados fuera del box. La página de reto de cada concursante les genera un enlace de lanzamiento personal al sitio externo; una resolución se reporta de vuelta al leaderboard, ya sea a través del callback propio de ese sitio o de una flag escrita de vuelta en la app. No necesita GitHub, ni forks, ni pipeline.
Alrededor de los módulos que actives, la plataforma proporciona: autorregistro
de equipos con capitanes, códigos de unión y enlaces /join/<code> (jugar en solitario es un equipo de uno; una flag resuelta por varios compañeros de equipo cuenta una vez); el
leaderboard en vivo con un gráfico de puntuación a lo largo del tiempo estilo CTFd a partir de timestamps reales por resolución; el
panel /admin con lista de permitidos — congelación, ventanas de puntuación y registro, pistas y costes, límite de equipos, cooldowns, contenido de módulos, acciones de soporte por concursante, un flujo de actividad y métricas de participación — todo en tiempo de ejecución, sin
recompilar; y un registro de auditoría con límite en cada acción de admin.
| Desglose del concursante | Navegador de retos |
|---|---|
![]() | ![]() |
| Tablero de flags de Jeopardy | Quiz |
|---|---|
![]() | ![]() |
Capturado desde la app para concursantes ejecutándose localmente mediante scripts/dev-stack up
con jugadores de demo precargados. Los targets y los enlaces de fork se rigen por la configuración del evento; el
nombre del evento y el resto de su branding son ajustes del panel de admin.
Un stack de Docker Compose: Caddy termina TLS delante de la app Next.js;
la app habla con Redis solo a través de srh (un proxy REST compatible con Upstash) —
la red está dividida de modo que nada expuesto a internet tenga ruta a redis:6379.
Quiz, Jeopardy y AI califican dentro de la app y depositan puntos directamente en Redis.
Secure Development se califica fuera del box: el fork del concursante ejecuta una
GitHub Action que arranca el target, ejecuta la rúbrica contra el parche y
publica un comentario de puntuación legible por máquina en el PR. El poller sync extrae
esos comentarios — cero superficie de red entrante, así que el box funciona detrás de NAT y
en el wifi del recinto (ese es el único transporte: la ingesta push se eliminó en v0.6,
ver #377). La puntuación
entra a través de un único escritor auditado:
el POST /score con bearer auth del scorer, que valida y escribe
de forma monotónica — las resoluciones nunca se deshacen por una ejecución fallida posterior.
El panorama completo — componentes, el flujo de datos de puntuación en nueve pasos, el modelo de seguridad — está en docs/architecture.md.
El contenido de este módulo es un conjunto de targets vulnerables y sus rúbricas
de puntuación. Los concursantes eligen un target, hacen fork de la copia de la org, lo parchean y
abren un PR. Los retos de cada target son suites ejecutables de node:test, con precio
según dificultad.
Los recuentos se mantienen a mano y están fijados a la rúbrica vendorizada por
apps/web/src/lib/tests/apps-catalogue.test.ts — vuelve a comprobarlos
tras una actualización de vendor-rubric.sh. Los parches de referencia
que demuestran que una corrección correcta puntúa (la puerta en dirección positiva) viven por separado
bajo patches/.
Las rúbricas viven en scorer/rubric.owasp/, vendorizadas desde
OWASP-CTF/dc34-owasp-secure-development-ctf
y fijadas al único commit upstream registrado en
scorer/rubric.owasp/PROVENANCE.md. Vuelve a vendorizar contra un commit más reciente con:```sh
./scripts/vendor-rubric.sh --all --ref
Se admiten dos formas de rúbrica a la vez, y un único directorio de rúbrica puede mezclarlas: los archivos `<target>.yaml` usan la gramática declarativa de sonda de petición/expectativa HTTP, y los directorios `<target>/tests/challenges/` usan pruebas ejecutables valoradas por `catalogue.<target>.json`. Guía de autoría:
[docs/scorer.md](https://github.com/owasp/owasp-ctf-in-a-box/blob/main/docs/scorer.md).
**Sobre el secreto de las rúbricas.** Estas rúbricas son públicas. Los objetivos son de código abierto y sus soluciones ya están publicadas, por lo que el kit trata la privacidad de la rúbrica como una protección contra el juego con las comprobaciones más que contra el conocimiento de las respuestas — una concesión aceptada para un evento autoalojado. Sustitúyela por tu propia rúbrica privada en cualquier momento:```sh
cp -r /path/to/private-rubric scorer/rubric
docker build -t ghcr.io/<org>/score:latest --build-arg RUBRIC_DIR=rubric scorer/
scorer/rubric/ está en gitignore y reservado exactamente para esto.
Una vez que el stack esté levantado en tu EVENT_URL:
/admin: congelar la clasificación, abrir y cerrar
el registro, establecer el calendario, redactar preguntas de quiz, retos classic
y retos ai — y cuando un concursante se queda atascado, arreglar a ese concursante
en concreto en lugar de reiniciar el evento.docker compose logs -f sync (se ejecuta con
secure-development habilitado). Todo el estado vive en volúmenes Docker con
nombre, así que un reinicio de la máquina no pierde nada../setup/ctf-setup.sh teardown archiva los repositorios
objetivo — luego desinstala la GitHub App y elimina los secretos de Actions de la
organización tú mismo. Un evento sin secure-development no tiene forks que
archivar.Los equipos, el panel de administración, la verificación del kit antes del día y el stack de desarrollo local están cubiertos en docs/operations.md; los requisitos previos, el transporte de puntuaciones, la configuración de OAuth y la configuración del evento en docs/hosting.md.
El razonamiento completo, las alternativas y los compromisos están registrados como ADRs numerados en docs/decisions.md.
Renderizado en owasp.github.io/owasp-ctf-in-a-box.
Las contribuciones son bienvenidas — CONTRIBUTING.md cubre el entorno de desarrollo, las puertas de CI y cómo proponer un módulo; CODE_OF_CONDUCT.md aplica.
Los agentes deben seguir AGENTS.md. Los comandos de abajo coinciden con
CI; make help lista los mismos objetivos.
Cada servicio se prueba de forma independiente (Node 22 en todos):```sh (cd sync && npm ci && npm test) (cd scorer && npm ci && npm test && node tools/vacuous-sweep.mjs) ./scripts/acceptance-scorer.sh # from the repo root — the script lives in scripts/ (cd apps/web && corepack pnpm install --frozen-lockfile && corepack pnpm lint && corepack pnpm test) ./scripts/smoke.sh # the full poll pipeline, end to end
¿Encontraste una vulnerabilidad en el propio kit? **[SECURITY.md](https://github.com/owasp/owasp-ctf-in-a-box/blob/main/SECURITY.md)** — las
vulnerabilidades de los objetivos son intencionales y quedan fuera del alcance.
## Licencia y créditos
MIT — consulta [LICENSE](https://github.com/owasp/owasp-ctf-in-a-box/blob/main/LICENSE). El contenido del rubric bajo `scorer/rubric.owasp/`
es vendored del evento upstream
[OWASP-CTF](https://github.com/OWASP-CTF/dc34-owasp-secure-development-ctf),
fijado al commit en `scorer/rubric.owasp/PROVENANCE.md` — este kit
existe porque ese evento merecía ejecutarse más de una vez. Los objetivos
vulnerables no son vendored: los eventos los bifurcan desde sus propios upstreams
([Juice Shop](https://github.com/juice-shop/juice-shop),
[WebGoat](https://github.com/WebGoat/WebGoat),
[DVWA](https://github.com/digininja/DVWA),
[Security Shepherd](https://github.com/OWASP/SecurityShepherd),
[VulnerableApp](https://github.com/SasanLabs/VulnerableApp),
[VAmPI](https://github.com/erev0s/VAmPI)), y cada uno conserva su propia licencia.
OWASP® es una marca registrada de la OWASP Foundation; este proyecto no está
afiliado ni respaldado por ella.
| Target | Retos | Puntos | Notas |
|---|
vulnerableapp | 110 | 187 | El target más grande; puntuado en 8 vías en paralelo |
webgoat | 69 | 137 | Build en dos etapas: Maven, luego el Dockerfile solo de runtime del fork |
dvwa | 55 | 108 | Necesita un contenedor hermano MariaDB y una inicialización de esquema |
securityshepherd | 40 | 79 | HTTPS, stack de tres contenedores, estrictamente en serie |
juice-shop | 38 | 141 | El único target cuya dificultad llega a 6 estrellas |
vampi | 9 | 16 | Autocontenido; la prueba de extremo a extremo más rápida |
| Total | 321 | 668 | Cada evento aprovisiona los seis; elige un subconjunto en /admin → Secure Development → Targets |
| Lee esto cuando… | Documento |
|---|
| Estés levantando el kit | docs/hosting.md — requisitos previos, el asistente y cada paso discreto, cómo llegan las puntuaciones a la máquina, la app de GitHub OAuth, configuración del evento |
| Estés desplegando en la nube | docs/aws.md (Terraform: ECS Fargate + ElastiCache + ALB) · docs/fly.md (una máquina Fly) |
| Estés a punto de abrir las puertas | docs/security-checklist.md — el recorrido pre-evento de una página |
| Estés ejecutando el evento | docs/operations.md — equipos, el panel de administración, las guías de organizador de quiz/classic/ai, verificación, teardown |
| Quieras entender el sistema | docs/architecture.md — diagrama, flujo de datos de puntuación, claves de Redis, modelo de seguridad, estrategia de testing |
| Estés escribiendo una rúbrica | docs/scorer.md — modos serve + judge, ambas gramáticas de rúbrica, autoría y build |
| Estés construyendo un nuevo módulo | docs/modules.md — el contrato plataforma/módulo |
| Te preguntes "¿por qué es así?" | docs/decisions.md — ADRs numerados |