
Kit CTF OWASP auto-hébergé : une machine, une organisation GitHub gratuite, aucune dépendance cloud
Un plan de contrôle auto-hébergé pour les événements d'apprentissage de la sécurité — une machine, une organisation GitHub gratuite.
Faites-le tourner pour une université, un lycée, une branche OWASP, un meetup.
Lisez AGENTS.md avant d'écrire du code. C'est le manuel
d'utilisation : les commandes exactes que la CI exécute, les modes de
défaillance que ce dépôt a déjà rencontrés, et les invariants de revue dans
docs/reviewing.md. CLAUDE.md est un pointeur vers
le même fichier.
Un changement est prêt lorsque la CI est verte et que chaque fil CodeRabbit actionnable sur le dernier commit est résolu (ou refusé officiellement). Les commits suivent les Conventional Commits et ne portent aucune attribution d'IA.
Le travail petit et bien spécifié est étiqueté
good first issue.
Les nouveaux modules commencent par une issue, pas par une PR — voir
CONTRIBUTING.md.
Un plan de contrôle, pas un jeu unique. La machine donne à un événement sa colonne vertébrale partagée — une organisation GitHub, l'inscription des équipes, un classement en direct, un panneau d'administration pour les organisateurs, et le pipeline de scoring qui l'alimente. Les modules branchent le contenu des défis sur cette colonne vertébrale, et n'importe quel sous-ensemble peut tourner seul ou conjointement : le Secure Development avec correction-pour-score, une banque de Quiz, un plateau Jeopardy, et des défis IA hébergés en externe. Le contrat de module est la frontière entre la colonne vertébrale et le contenu, de sorte que la machine est conçue pour héberger d'autres modules — forensics, sécurité des API, cloud — au fur et à mesure de leur arrivée.
Pourquoi il existe. Le module Secure Development enseigne la défense plutôt que l'attaque, et c'est une excellente façon d'enseigner le codage sécurisé. Jusqu'à présent, en faire tourner un signifiait déployer Vercel, Upstash, Lambda et DynamoDB, assumer la facture cloud, et avoir accès à une image de scoring privée. C'est une demande raisonnable pour une conférence avec un budget. C'est une demande déraisonnable pour un cours de sécurité universitaire, un club de lycée, une soirée de branche OWASP, ou un atelier de week-end.
Ce kit supprime cela. Tout tourne depuis Docker Compose sur une machine que vous possédez déjà — un ordinateur portable, un bureau de rechange, un petit VPS — plus une organisation GitHub gratuite pour les forks. Les grilles d'évaluation des six cibles sont livrées dans la machine, donc il n'y a aucune image privée à demander et aucun code de scoring à écrire. Rien n'est facturé, rien ne communique avec l'extérieur, et lorsque l'événement se termine vous archivez les dépôts et arrêtez la stack.
Pour qui c'est : quiconque veut organiser cet événement et ne veut pas devenir opérateur cloud pour le faire — instructeurs de cours, organisateurs de clubs, responsables de branches OWASP, animateurs d'ateliers, équipes de sécurité organisant une journée de formation interne.
Déployé et exercé de bout en bout ; pas encore exécuté pour une vraie cohorte. Le
chemin de scoring complet est livré dans le kit — le POST /score authentifié par bearer du scorer, le
workflow de scoring autonome pour les forks, le transport par polling — et
scripts/smoke.sh pilote tout ce pipeline contre des mocks. Au-delà,
le kit tourne en continu sur une machine hébergée à partir du même fichier Compose que ce
dépôt livre, GET /health rapporte la révision exacte qui le sert, et une
passe de bout en bout sur cette instance live est là où un lot de défauts réels ont été
trouvés et corrigés — du genre qu'une suite mockée ne peut pas voir.
Ce qui n'a pas eu lieu, c'est un vrai événement : une cohorte de participants ouvrant de vraies PR contre de vrais forks, en même temps, pendant des heures. C'est l'écart entre « le pipeline fonctionne » et « le pipeline fonctionne à 40 personnes ». Deux réserves sont ouvertes plutôt qu'enfouies : le matcher de résultats Security Shepherd a une limite résiduelle déclarée (un refus formulé de manière inhabituelle peut encore être lu comme une résolution — il peut sous-créditer un patch correct, jamais attribuer un point gratuit), et le profil de charge d'une cohorte complète n'est pas testé. Détails et état actuel : Statut et dépendances amont.
Ce qu'il fait que ceux-là ne font pas : formation à la défense par correction-pour-score notée via des pull requests GitHub, un contrat de module pour mélanger les types de jeux sur un seul classement, et un plan de contrôle que vous possédez de bout en bout — une machine, une organisation gratuite, aucune facture cloud, aucune télémétrie.
Ce projet n'est pas affilié à la OWASP Foundation ni approuvé par elle. Quatre des six cibles vulnérables sont des projets OWASP (Juice Shop, WebGoat, Security Shepherd, VulnerableApp) ; DVWA et VAmPI sont des projets communautaires.
Voyez-le tourner en deux minutes — pas d'organisation GitHub, pas d'application OAuth, rien à
configurer. Vous avez besoin de Docker avec Compose v2 et openssl :```sh
git clone https://github.com/OWASP/owasp-ctf-in-a-box
cd owasp-ctf-in-a-box
./scripts/dev-stack up
Il écrit des secrets locaux jetables, construit les images du scorer et de l'application, démarre la stack, initialise un classement de démonstration via l'API de scoring réelle du scorer, et affiche l'URL à ouvrir. Vous devriez voir le classement avec les équipes initialisées et un graphique des scores dans le temps ; `./scripts/dev-stack score <login> juice-shop 3` ajoute trois résolutions supplémentaires en direct. `./scripts/dev-stack down` démonte le tout.
**Lancer un événement réel** avec l'assistant guidé. Ajoutez la **[CLI
`gh`](https://cli.github.com)** (authentifiée), plus **une organisation GitHub gratuite** si l'événement utilise le Secure Development ; `./setup/ctf-setup.sh check` vérifie d'abord l'outillage :```sh
./setup/ctf-setup.sh # guided, prompts for values, resumable
Il demande chaque valeur au fur et à mesure — l'URL de votre box, l'organisation de l'événement, les identifiants admin, si vous utilisez Secure Development, les identifiants GitHub — écrit .env, effectue toutes les étapes automatisables, vous guide à travers celles qui passent par l'interface GitHub, et reprend si vous vous arrêtez et revenez plus tard. Tout le reste (le nom de l'événement, quels modules s'exécutent, quelles cibles) est un réglage d'exécution dans /admin, il n'y a donc aucun fichier de configuration à modifier. Il ne demande que ce dont vous avez réellement besoin : un événement sans Secure Development n'a besoin ni d'organisation, ni de forks, ni d'image de scorer, et ces questions ne lui sont jamais posées. Prévisualisez toute étape modifiante avec --dry-run — il narre les étapes 4 à 9 à partir d'un .env déjà complet, et refuse (par conception) lorsqu'il n'y a pas d'identifiant admin, ou lorsque Secure Development est activé sans organisation. L'assistant se termine en exécutant ./setup/ctf-setup.sh doctor — une matrice d'état par fork que vous pouvez relancer à tout moment — puis propose un déploiement fly.io optionnel (par défaut non), de sorte que mettre le même événement sur un nom d'hôte public est un flux guidé — le nom d'hôte, un déploiement prévisualisé, puis une confirmation — plutôt qu'un parcours dans la documentation de déploiement.
Vous voulez les détails ? Chaque sous-commande distincte, chaque étape réservée à l'interface, et en quoi les deux applications GitHub diffèrent :
docs/hosting.md.
Plutôt dans un cloud ? docs/aws.md (Terraform : ECS Fargate, ElastiCache et un ALB — apply pour monter / destroy pour démonter) ou
docs/fly.md (une seule machine Fly).
Secure Development — forkez une application délibérément vulnérable, trouvez la faille, corrigez-la, ouvrez une PR. Une GitHub Action dans le fork exécute la grille d'évaluation de la cible sur le patch et le score arrive sur le classement (~30 s plus tard en mode polling). Six cibles, 321 défis ; l'état initial vaut 0, un patch correct rapporte ses points — verrouillé dans les deux sens. Nécessite l'organisation GitHub et le pipeline de scoring.
Quiz — questions de sécurité à choix unique et multiple, notées dans l'application au moment où elles sont répondues (tout ou rien en choix multiple), avec un plafond de tentatives et un délai de réessai. Créées depuis /admin une par une ou importées et exportées sous forme d'un seul bundle JSON. Ne nécessite ni GitHub, ni forks, ni pipeline.
Jeopardy — un tableau de flags rédigés par les organisateurs, par catégories. Les soumissions sont nettoyées et normalisées, la casse est tolérée sauf si un flag est marqué comme sensible à la casse (sa carte l'indique), avec un délai entre soumissions et des indices payants optionnels. Même création via /admin + bundle JSON que le quiz. Ne nécessite pas GitHub non plus.
AI — défis de prompt-injection et de guardrails hébergés en dehors de la box. La page de défi de chaque participant lui génère un lien de lancement personnel vers le site externe ; une résolution est reportée au classement, soit via le callback propre à ce site, soit via un flag saisi dans l'application. Ne nécessite ni GitHub, ni forks, ni pipeline.
Autour des modules que vous activez, la plateforme fournit : l'auto-inscription des équipes avec capitaines, codes d'accès et liens /join/<code> (le jeu en solo est une équipe d'une personne ; un flag résolu par plusieurs coéquipiers ne compte qu'une fois) ; le classement en direct avec un graphique de score dans le temps à la CTFd à partir des horodatages réels de chaque résolution ; le panneau /admin à liste d'autorisation — gel, fenêtres de scoring et d'inscription, indices et coûts, plafond d'équipes, délais, contenu des modules, actions de support par participant, un flux d'activité et des métriques d'engagement — le tout à l'exécution, sans reconstruction ; et un journal d'audit plafonné sur chaque action d'admin.
| Détail d'un participant | Navigateur de défis |
|---|---|
![]() | ![]() |
| Tableau de flags Jeopardy | Quiz |
|---|---|
![]() | ![]() |
Capturé depuis l'application participant exécutée localement via scripts/dev-stack up
avec des joueurs de démonstration préchargés. Les cibles et les liens de fork sont pilotés par la configuration de l'événement ; le nom de l'événement et le reste de son image de marque sont des réglages du panneau d'admin.
Une seule stack Docker Compose : Caddy termine le TLS devant l'application Next.js ;
l'application ne parle à Redis que via srh (un proxy REST compatible Upstash) —
le réseau est segmenté pour que rien d'exposé à Internet n'ait de route vers redis:6379.
Quiz, Jeopardy et AI notent à l'intérieur de l'application et enregistrent les points directement dans Redis.
Secure Development est noté en dehors de la box : le fork du participant exécute une
GitHub Action qui démarre la cible, exécute la grille d'évaluation sur le patch, et
publie un commentaire de score lisible par machine sur la PR. Le poller sync récupère
ces commentaires — zéro surface réseau entrante, donc la box fonctionne derrière un NAT et
sur le wifi du lieu (c'est le seul transport : l'ingestion par push a été supprimée en v0.6,
voir #377). Le score
entre par un unique writer audité :
le POST /score du scorer, authentifié par bearer, qui valide et écrit de manière
monotone — une résolution n'est jamais annulée par une exécution ultérieure en échec.
Le tableau complet — composants, le flux de données de score en neuf étapes, le modèle de sécurité — se trouve dans docs/architecture.md.
Le contenu de ce module est un ensemble de cibles vulnérables et de leurs grilles
d'évaluation de scoring. Les participants choisissent une cible, forkent la copie de l'organisation, la patchent et
ouvrent une PR. Les défis de chaque cible sont des suites node:test exécutables, tarifées
par difficulté.
Les comptages sont maintenus à la main et épinglés à la grille d'évaluation vendored par
apps/web/src/lib/tests/apps-catalogue.test.ts — revérifiez-les
après une mise à jour de vendor-rubric.sh. Les patches de référence
qui prouvent qu'une correction correcte marque des points (le verrou dans le sens positif) se trouvent séparément
sous patches/.
Les grilles d'évaluation se trouvent dans scorer/rubric.owasp/, vendored depuis
OWASP-CTF/dc34-owasp-secure-development-ctf
et épinglées au commit upstream unique enregistré dans
scorer/rubric.owasp/PROVENANCE.md. Re-vendorisez contre un commit plus récent avec :```sh
./scripts/vendor-rubric.sh --all --ref
Deux formes de rubriques sont prises en charge simultanément, et un même répertoire de rubriques peut les mélanger : les fichiers `<target>.yaml` utilisent la grammaire déclarative de sonde requête/réponse HTTP, et les répertoires `<target>/tests/challenges/` utilisent des tests exécutables tarifés par `catalogue.<target>.json`. Guide de rédaction :
[docs/scorer.md](https://github.com/owasp/owasp-ctf-in-a-box/blob/main/docs/scorer.md).
**Sur le secret des rubriques.** Ces rubriques sont publiques. Les cibles sont open source et leurs solutions sont déjà publiées, donc le kit traite la confidentialité des rubriques comme une protection contre le check-gaming plutôt que contre la connaissance des réponses — un compromis accepté pour un événement auto-hébergé. Remplacez à tout moment par votre propre rubrique privée :```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 gitignoré et réservé exactement à cela.
Une fois la stack démarrée à votre EVENT_URL :
/admin : figer le classement, ouvrir et fermer
les inscriptions, définir le planning, rédiger les questions de quiz, les défis classiques
et les défis IA — et quand un participant est bloqué, corriger ce seul
participant plutôt que de réinitialiser l'événement.docker compose logs -f sync (il tourne avec
secure-development activé). Tout l'état réside dans des volumes Docker nommés, donc
un redémarrage de la machine ne perd rien../setup/ctf-setup.sh teardown archive les dépôts
cibles — puis désinstallez vous-même la GitHub App et supprimez les secrets Actions de l'organisation.
Un événement sans secure-development n'a pas de forks à archiver.Les équipes, le panneau d'administration, la vérification du kit avant le jour J et la stack de développement locale sont tous couverts dans docs/operations.md ; les prérequis, le transport des scores, la configuration OAuth et la configuration de l'événement dans docs/hosting.md.
Le raisonnement complet, les alternatives et les compromis sont consignés sous forme d'ADR numérotés dans docs/decisions.md.
Rendu sur owasp.github.io/owasp-ctf-in-a-box.
Contributions bienvenues — CONTRIBUTING.md couvre l'environnement de développement, les portes CI et comment proposer un module ; CODE_OF_CONDUCT.md s'applique.
Les agents doivent suivre AGENTS.md. Les commandes ci-dessous correspondent à la CI ;
make help liste les mêmes cibles.
Chaque service se teste indépendamment (Node 22 partout) :```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
Vous avez trouvé une vulnérabilité dans le kit lui-même ? **[SECURITY.md](https://github.com/owasp/owasp-ctf-in-a-box/blob/main/SECURITY.md)** — les vulnérabilités des cibles sont intentionnelles et hors périmètre.
## Licence et crédits
MIT — voir [LICENSE](https://github.com/owasp/owasp-ctf-in-a-box/blob/main/LICENSE). Le contenu du barème sous `scorer/rubric.owasp/`
est vendored depuis l'événement amont
[OWASP-CTF](https://github.com/OWASP-CTF/dc34-owasp-secure-development-ctf),
épinglé au commit dans `scorer/rubric.owasp/PROVENANCE.md` — ce kit
existe parce que cet événement valait la peine d'être organisé plus d'une fois. Les cibles
vulnérables ne sont pas vendored : les événements les forkent depuis leurs propres amonts
([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)), et chacune conserve sa propre licence.
OWASP® est une marque déposée de la OWASP Foundation ; ce projet n'est ni
affilié à celle-ci ni approuvé par elle.
| Cible | Défis | Points | Notes |
|---|
vulnerableapp | 110 | 187 | Plus grande cible ; notée en 8 voies parallèles |
webgoat | 69 | 137 | Build en deux étapes : Maven, puis le Dockerfile runtime-only du fork |
dvwa | 55 | 108 | Nécessite un conteneur MariaDB associé et une initialisation de schéma |
securityshepherd | 40 | 79 | HTTPS, stack à trois conteneurs, strictement séquentiel |
juice-shop | 38 | 141 | La seule cible dont la difficulté va jusqu'à 6 étoiles |
vampi | 9 | 16 | Autonome ; la preuve de bout en bout la plus rapide |
| Total | 321 | 668 | Chaque événement provisionne les six ; choisissez un sous-ensemble dans /admin → Secure Development → Targets |
| À lire quand vous… | Document |
|---|
| Installez le kit | docs/hosting.md — prérequis, l'assistant et chaque étape discrète, comment les scores atteignent la machine, l'application GitHub OAuth, la configuration de l'événement |
| Déployez dans le cloud | docs/aws.md (Terraform : ECS Fargate + ElastiCache + ALB) · docs/fly.md (une machine Fly) |
| Êtes sur le point d'ouvrir les portes | docs/security-checklist.md — la vérification pré-événement d'une page |
| Lancez l'événement | docs/operations.md — équipes, le panneau d'administration, les guides organisateur quiz/classique/IA, vérification, démontage |
| Cherchez à comprendre le système | docs/architecture.md — diagramme, flux des données de score, clés Redis, modèle de sécurité, stratégie de test |
| Rédigez une grille d'évaluation | docs/scorer.md — modes serve + judge, les deux grammaires de grille, rédaction et build |
| Construisez un nouveau module | docs/modules.md — le contrat plateforme/module |
| Vous demandez « pourquoi est-ce ainsi ? » | docs/decisions.md — ADR numérotés |