Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,16 @@ http://<IP_TAILSCALE_VM>: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

Expand Down
3 changes: 3 additions & 0 deletions challenges/3-Red-Team-Binary-Vault/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Secrets reels du challenge -- ne JAMAIS committer (voir challenge.env.example)
challenge.env

Comment on lines +1 to +3
# Artefacts generes au build -- ne pas committer.
secret.h
flag1.txt
Expand Down
12 changes: 10 additions & 2 deletions challenges/3-Red-Team-Binary-Vault/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 && \
Expand Down
10 changes: 9 additions & 1 deletion challenges/3-Red-Team-Binary-Vault/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
29 changes: 29 additions & 0 deletions challenges/3-Red-Team-Binary-Vault/docs/ADMIN_DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
22 changes: 22 additions & 0 deletions challenges/3-Red-Team-Binary-Vault/setup/challenge.env.example
Original file line number Diff line number Diff line change
@@ -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
86 changes: 69 additions & 17 deletions challenges/3-Red-Team-Binary-Vault/setup/gen_secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__":
Expand Down