diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..fa4dba7 --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +# ===================================================================== +# Modele de variables d'environnement pour RootMeUp. +# Copiez ce fichier en .env et renseignez vos valeurs. +# .env est gitignore : ne JAMAIS y mettre de secrets committes. +# ===================================================================== + +# --- CTFd --- +# Cle secrete de session CTFd (generer une valeur aleatoire forte) +CTFD_SECRET_KEY=change_me +# URL/DSN de la base de donnees CTFd +DATABASE_URL=mysql+pymysql://ctfd:change_me@db/ctfd +# URL Redis (cache / file de jobs) +REDIS_URL=redis://cache:6379 + +# --- Plugin conteneurs (CTFdDockerContainersPlugin) --- +# Hote Docker expose aux instances de challenges +DOCKER_HOSTNAME=100.118.132.76 +# Plage de ports allouee dynamiquement aux instances +DOCKER_PORT_RANGE=32768-60999 + +# --- Secrets par challenge --- +# Les flags ne sont PAS definis ici : chaque challenge fournit ses secrets +# via son propre fichier gitignore (ex: challenges/3-Red-Team-Binary-Vault/ +# setup/challenge.env). Voir le challenge.env.example de chaque challenge. diff --git a/README.md b/README.md index 4b596b8..00d162b 100644 --- a/README.md +++ b/README.md @@ -55,12 +55,16 @@ http://:8000 ## Challenges disponibles -| # | Nom | Catégorie | Statut | -|---|-----|-----------|--------| -| 1 | Phishing sur corp.local — analyse de logs post-incident | Blue Team | Intégré | -| 2 | Mémoire et analyse de malware (Volatility) | Blue Team | Intégré | -| 3 | LFI jusqu'au flag | Red Team | En cours | -| 4 | Détection dans les logs (SIEM / ELK) | Blue Team | En cours | +Numérotation par équipe (`N-Blue-Team-*` / `N-Red-Team-*`). + +| # | Dossier | Nom | Catégorie | Statut | +|---|---------|-----|-----------|--------| +| Blue 1 | `challenges/ctf-forensics-dfir-elk` ¹ | Phishing sur corp.local — analyse de logs (ELK) | Blue Team | Intégré | +| Blue 2 | `challenges/2-Blue-Team-Memory-Forensics` | Mémoire et analyse de malware (Volatility) | Blue Team | Intégré | +| Red 3 | `challenges/3-Red-Team-Binary-Vault` | VAULT-9 — reverse + exploitation binaire (ret2win) | Red Team | Développé, testé | +| Red 1-2 | _à pousser_ | Exploitation machine Linux / Web (pivot & root) | Red Team | En cours | + +¹ à renommer `1-Blue-Team-Phishing-Corp-Local` sur la branche du challenge. ## Ajout d'un challenge sur le serveur diff --git a/challenges/3-Red-Team-Binary-Vault/.gitignore b/challenges/3-Red-Team-Binary-Vault/.gitignore index b575f4e..79e2095 100644 --- a/challenges/3-Red-Team-Binary-Vault/.gitignore +++ b/challenges/3-Red-Team-Binary-Vault/.gitignore @@ -1,3 +1,6 @@ +# Secrets reels du challenge -- ne JAMAIS committer (voir challenge.env.example) +challenge.env + # Artefacts generes au build -- ne pas committer. secret.h flag1.txt diff --git a/challenges/3-Red-Team-Binary-Vault/Dockerfile b/challenges/3-Red-Team-Binary-Vault/Dockerfile index a7faa7b..8d9cc03 100644 --- a/challenges/3-Red-Team-Binary-Vault/Dockerfile +++ b/challenges/3-Red-Team-Binary-Vault/Dockerfile @@ -10,12 +10,20 @@ RUN apt-get update && \ RUN useradd -m -s /usr/sbin/nologin ctf +# Secrets fournis au build (priorite sur setup/challenge.env). Vides par +# defaut -> gen_secret.py utilise challenge.env ou des placeholders. +ARG LICENSE="" +ARG FLAG1="" +ARG FLAG2="" +ARG XOR_KEY="" + WORKDIR /build COPY challenge/vault.c ./vault.c -COPY setup/gen_secret.py ./gen_secret.py +COPY setup/ ./setup/ # Genere secret.h + flags, compile, installe dans /challenge. -RUN python3 gen_secret.py && \ +RUN LICENSE="$LICENSE" FLAG1="$FLAG1" FLAG2="$FLAG2" XOR_KEY="$XOR_KEY" \ + python3 setup/gen_secret.py && \ gcc -fno-stack-protector -no-pie -fno-pie -O0 -w -o vault vault.c && \ mkdir -p /challenge && \ cp vault /challenge/vault && \ diff --git a/challenges/3-Red-Team-Binary-Vault/docker-compose.yml b/challenges/3-Red-Team-Binary-Vault/docker-compose.yml index cfbde16..311f9fa 100644 --- a/challenges/3-Red-Team-Binary-Vault/docker-compose.yml +++ b/challenges/3-Red-Team-Binary-Vault/docker-compose.yml @@ -2,7 +2,15 @@ # CTFdDockerContainersPlugin gere l'allocation dynamique du port. services: vault: - build: . + build: + context: . + # Optionnel : surcharge les secrets via l'environnement du shell. + # Sinon, gen_secret.py lit setup/challenge.env (voir .env.example). + args: + LICENSE: ${LICENSE:-} + FLAG1: ${FLAG1:-} + FLAG2: ${FLAG2:-} + XOR_KEY: ${XOR_KEY:-} container_name: rt3-vault ports: - "9003:9003" diff --git a/challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md b/challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md index 5e01604..9ad0496 100644 --- a/challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md +++ b/challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md @@ -15,6 +15,35 @@ Les secrets (licence, flags) sont générés au build par `setup/gen_secret.py` > ✅ **Validé bout-en-bout en conteneur Docker le 20/07/2026** : build OK, > service accessible sur 9003, exploit `solution/exploit.py` récupère les 2 flags. +## Fournir les flags (ne PAS les committer) + +Les vrais flags **ne sont plus stockés dans le dépôt**. Avant de builder, fournissez-les +par l'un des deux moyens (l'environnement a priorité sur le fichier) : + +**Option A — fichier `challenge.env` (recommandé)** +```bash +cd challenges/3-Red-Team-Binary-Vault/setup +cp challenge.env.example challenge.env +$EDITOR challenge.env # renseigner LICENSE, FLAG1, FLAG2 +``` +`challenge.env` est gitignoré : il ne partira jamais dans git. Il est inclus dans le +contexte de build et lu automatiquement par `gen_secret.py`. + +**Option B — build-args / variables d'environnement** +```bash +docker build -t rt3-vault \ + --build-arg LICENSE='...' --build-arg FLAG1='RM{...}' --build-arg FLAG2='RM{...}' . +# ou, avec docker-compose : export FLAG1=... FLAG2=... LICENSE=... puis docker-compose build +``` + +Si aucune source n'est fournie, le build **réussit quand même** mais avec des flags +**placeholders** (`RM{PLACEHOLDER_...}`) et un avertissement — utile pour un test à blanc, +inutilisable en prod. + +> ⚠️ **Rotation** : les flags d'origine ont été committés publiquement avant ce changement. +> Ils restent visibles dans l'historique git. Choisissez de **nouveaux flags** dans +> `challenge.env` (et mettez-les à jour dans CTFd) pour que les anciens deviennent inutiles. + ## Prérequis / pièges courants - **Compose v1 vs v2** : `docker compose` (avec espace) n'existe qu'avec le plugin v2. diff --git a/challenges/3-Red-Team-Binary-Vault/setup/challenge.env.example b/challenges/3-Red-Team-Binary-Vault/setup/challenge.env.example new file mode 100644 index 0000000..d343ac8 --- /dev/null +++ b/challenges/3-Red-Team-Binary-Vault/setup/challenge.env.example @@ -0,0 +1,22 @@ +# ===================================================================== +# Modele de configuration des secrets du challenge VAULT-9. +# +# 1. Copiez ce fichier : cp challenge.env.example challenge.env +# 2. Renseignez les VRAIES valeurs dans challenge.env +# 3. challenge.env est gitignore : il ne sera jamais committe. +# +# Alternative : passer ces valeurs en --build-arg au docker build +# (les variables d'environnement ont priorite sur ce fichier). +# ===================================================================== + +# Licence attendue par l'etape reverse (etape 1) +LICENSE=exemple_a_changer + +# Flag de l'etape reverse (etape 1) +FLAG1=RM{exemple_flag_reverse_a_changer} + +# Flag de l'etape exploitation ret2win (etape 2) +FLAG2=RM{exemple_flag_pwn_a_changer} + +# Cle de XOR mono-octet (0x.. ou decimal). Non sensible, defaut 0x5c. +XOR_KEY=0x5c diff --git a/challenges/3-Red-Team-Binary-Vault/setup/gen_secret.py b/challenges/3-Red-Team-Binary-Vault/setup/gen_secret.py index 8e65aea..cbc2575 100644 --- a/challenges/3-Red-Team-Binary-Vault/setup/gen_secret.py +++ b/challenges/3-Red-Team-Binary-Vault/setup/gen_secret.py @@ -2,39 +2,91 @@ # -*- coding: utf-8 -*- # ===================================================================== # gen_secret.py -- generateur mainteneur (execute au build Docker) -# /!\ SPOILER : contient la licence et les flags en clair. # -# Genere : -# - secret.h : la licence XORee (importee par vault.c) -# - flag1.txt : flag de l'etape reverse -# - flag2.txt : flag de l'etape exploitation +# Genere secret.h (licence XORee) + flag1.txt / flag2.txt. # -# Pour changer les flags/la licence, modifiez les constantes ci-dessous -# puis rebuildez l'image. Rien de sensible n'est ecrit dans le binaire. +# Les valeurs sensibles (licence, flags) ne sont PLUS ecrites en dur ici : +# elles proviennent, par ordre de priorite, +# 1. des variables d'environnement (ex: passees en --build-arg), +# 2. du fichier `challenge.env` place a cote de ce script (gitignore), +# 3. a defaut, de placeholders inoffensifs (le build reussit mais les +# flags ne sont pas les vrais -> un avertissement est affiche). +# +# Voir challenge.env.example pour le modele a copier en challenge.env. # ===================================================================== -LICENSE = "unl0ck_th3_v4ult" # cle attendue par check_license() -XOR_KEY = 0x5C # cle de XOR mono-octet -FLAG1 = "RM{r3v3rs3_l3_x0r_c0mm3_un_pr0}" -FLAG2 = "RM{r3t2w1n_l4_v4ult_3st_0uv3rt3}" +import os +import sys + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +ENV_FILE = os.path.join(SCRIPT_DIR, "challenge.env") + +# Placeholders : PAS les vrais flags. Le build reste fonctionnel pour les +# tests, mais ces valeurs doivent etre remplacees via challenge.env / build-arg. +PLACEHOLDERS = { + "LICENSE": "CHANGEME_license_a_definir", + "FLAG1": "RM{PLACEHOLDER_definir_dans_challenge_env}", + "FLAG2": "RM{PLACEHOLDER_definir_dans_challenge_env}", + "XOR_KEY": "0x5c", +} + + +def load_env_file(path): + """Parse simple d'un fichier KEY=VALUE (lignes vides / # ignorees).""" + values = {} + if not os.path.isfile(path): + return values + with open(path, encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, _, val = line.partition("=") + values[key.strip()] = val.strip().strip('"').strip("'") + return values + + +def resolve(name, file_values): + """env var (non vide) > challenge.env > placeholder.""" + env = os.environ.get(name) + if env: + return env, "env" + if file_values.get(name): + return file_values[name], "challenge.env" + return PLACEHOLDERS[name], "placeholder" def main(): - enc = ", ".join(str(b ^ XOR_KEY) for b in LICENSE.encode()) + file_values = load_env_file(ENV_FILE) + + license_, s1 = resolve("LICENSE", file_values) + flag1, s2 = resolve("FLAG1", file_values) + flag2, s3 = resolve("FLAG2", file_values) + xor_raw, _ = resolve("XOR_KEY", file_values) + xor_key = int(xor_raw, 0) & 0xFF # accepte 0x.. ou decimal + + if "placeholder" in (s1, s2, s3): + print("[gen_secret] /!\\ ATTENTION : valeurs par defaut (placeholders) " + "utilisees. Definissez challenge.env ou passez les build-args " + "(LICENSE, FLAG1, FLAG2). Voir challenge.env.example.", + file=sys.stderr) + + enc = ", ".join(str(b ^ xor_key) for b in license_.encode()) with open("secret.h", "w") as f: f.write("#ifndef SECRET_H\n#define SECRET_H\n") f.write("/* Genere par gen_secret.py -- ne pas editer a la main. */\n") - f.write(f"#define XOR_KEY 0x{XOR_KEY:02x}\n") - f.write(f"#define LICENSE_LEN {len(LICENSE)}\n") + f.write(f"#define XOR_KEY 0x{xor_key:02x}\n") + f.write(f"#define LICENSE_LEN {len(license_)}\n") f.write(f"static const unsigned char LICENSE_ENC[] = {{ {enc} }};\n") f.write("#endif\n") with open("flag1.txt", "w") as f: - f.write(FLAG1 + "\n") + f.write(flag1 + "\n") with open("flag2.txt", "w") as f: - f.write(FLAG2 + "\n") + f.write(flag2 + "\n") - print("[gen_secret] secret.h, flag1.txt, flag2.txt generes.") + print(f"[gen_secret] secret.h + flags generes " + f"(licence:{s1}, flag1:{s2}, flag2:{s3}).") if __name__ == "__main__":