From d5309fc6d322ce8bcd42fb5ca447b6ed255f8fa9 Mon Sep 17 00:00:00 2001 From: Jakub WERLINSKI Date: Fri, 24 Jul 2026 15:04:10 +0200 Subject: [PATCH] =?UTF-8?q?refactor(blue-memory):=20flag=20rot=C3=A9=20hor?= =?UTF-8?q?s=20d=C3=A9p=C3=B4t=20via=20challenge.env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le flag n'est plus codé en dur (il était public dans le dépôt). Il provient désormais de setup/challenge.env (gitignoré) ou de la variable d'env FLAG, sinon d'un placeholder inoffensif. - flag_config.py : résolution env > challenge.env > placeholder (partagé) - generate_challenge.py / generate_pcap.py : lisent le flag via resolve_flag() - generate_challenge.py : génère le hash SHA256 du flag (valout/flag.sha256) - validate_flag.py : lit le hash du flag depuis solution/flag.sha256 (build) - Dockerfile : copie le hash dans l'image (jamais le flag en clair) - challenge.env.example + .gitignore (challenge.env, valout/, flag.sha256) - docs/SOLUTION : flag générique (roté par déploiement) Testé : génération + validateur 100/100 avec un flag roté ; mode placeholder OK. --- .../.gitignore | 5 ++++ .../Dockerfile | 3 +++ .../docs/ADMIN_DEPLOYMENT.md | 21 ++++++++++++++-- .../setup/challenge.env.example | 13 ++++++++++ .../setup/flag_config.py | 25 +++++++++++++++++++ .../setup/generate_challenge.py | 11 +++++++- .../setup/generate_pcap.py | 3 ++- .../solution/SOLUTION.md | 9 ++++--- .../solution/validate_flag.py | 14 +++++++++-- 9 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/challenge.env.example create mode 100644 challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/flag_config.py diff --git a/challenges/2-Blue-Team-Memory-Forensics-Jakub/.gitignore b/challenges/2-Blue-Team-Memory-Forensics-Jakub/.gitignore index 87e29b1..f52ef15 100644 --- a/challenges/2-Blue-Team-Memory-Forensics-Jakub/.gitignore +++ b/challenges/2-Blue-Team-Memory-Forensics-Jakub/.gitignore @@ -1,7 +1,12 @@ +# Secret réel du challenge — ne JAMAIS committer (voir challenge.env.example) +challenge.env + # Fichiers générés au build Docker (recréés automatiquement) challenge/memory.dmp challenge/network_capture.pcap challenge/hints.txt +valout/ +solution/flag.sha256 # Export Docker (trop lourd pour git) *.tar diff --git a/challenges/2-Blue-Team-Memory-Forensics-Jakub/Dockerfile b/challenges/2-Blue-Team-Memory-Forensics-Jakub/Dockerfile index b5cdbf3..c103834 100644 --- a/challenges/2-Blue-Team-Memory-Forensics-Jakub/Dockerfile +++ b/challenges/2-Blue-Team-Memory-Forensics-Jakub/Dockerfile @@ -45,6 +45,9 @@ COPY report/ ./report/ # Copier le script de validation (le joueur peut valider ses réponses) COPY solution/validate_flag.py ./solution/validate_flag.py +# Hash SHA256 du flag (généré au build depuis challenge.env) — jamais le flag en clair +COPY --from=builder /build/valout/flag.sha256 ./solution/flag.sha256 + # Copier le README COPY README.md ./README.md diff --git a/challenges/2-Blue-Team-Memory-Forensics-Jakub/docs/ADMIN_DEPLOYMENT.md b/challenges/2-Blue-Team-Memory-Forensics-Jakub/docs/ADMIN_DEPLOYMENT.md index b5d4543..d7a407c 100644 --- a/challenges/2-Blue-Team-Memory-Forensics-Jakub/docs/ADMIN_DEPLOYMENT.md +++ b/challenges/2-Blue-Team-Memory-Forensics-Jakub/docs/ADMIN_DEPLOYMENT.md @@ -50,7 +50,7 @@ Challenge type: `container` ## 6) Creation du flag (obligatoire) Dans l'onglet `Flags`: - **Type**: `Static` -- **Valeur**: `blue{m3m_f0r3ns1cs_v0l4t1l1ty_m4st3r}` +- **Valeur**: la valeur définie dans `setup/challenge.env` (`FLAG=...`) — **pas** de flag en clair dans le dépôt. Voir « Fournir le flag » ci-dessous. - **Case Sensitive**: activé ## 7) Sequence de validation @@ -58,9 +58,26 @@ Dans l'onglet `Flags`: 2. Tester `Start Instance` avec un compte joueur. 3. Vérifier l'accès HTTP aux fichiers : `http://:8000/memory.dmp` 4. Vérifier que `hints.txt` et `network_capture.pcap` sont bien accessibles. -5. Soumettre le flag `blue{m3m_f0r3ns1cs_v0l4t1l1ty_m4st3r}` pour valider. +5. Soumettre le flag (celui de `challenge.env`) pour valider. 6. Passer en `Visible`. +## Fournir le flag (ne PAS le committer) + +Le flag n'est plus codé en dur : il est fourni au build via un fichier gitignoré, +puis cuit dans le dump + le PCAP, et son hash SHA256 est généré pour le validateur. + +```bash +cd setup +cp challenge.env.example challenge.env +$EDITOR challenge.env # FLAG=blue{...} (choisir une valeur rotée) +``` +Puis (re)builder l'image ; `gen`/`generate_challenge.py` lit `challenge.env` automatiquement. +Si aucun `challenge.env` n'est fourni, le build réussit mais avec un flag **placeholder** +(`blue{PLACEHOLDER_...}`). Le même flag doit être saisi côté CTFd (étape 6). + +> ⚠️ Rotation : l'ancien flag `blue{m3m_f0r3ns1cs_v0l4t1l1ty_m4st3r}` est resté public +> dans l'historique git — choisir une **nouvelle** valeur dans `challenge.env`. + ## 8) Troubleshooting Si l'instance ne démarre pas : diff --git a/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/challenge.env.example b/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/challenge.env.example new file mode 100644 index 0000000..992f965 --- /dev/null +++ b/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/challenge.env.example @@ -0,0 +1,13 @@ +# ===================================================================== +# Modele de configuration du flag — challenge Memory Forensics. +# +# 1. Copiez : cp challenge.env.example challenge.env +# 2. Mettez le VRAI flag (roté) dans challenge.env +# 3. challenge.env est gitignore : il ne sera jamais committe. +# +# Le flag est ensuite cuit dans le dump/PCAP au build, et son hash +# SHA256 est genere pour le validateur. Alternative : passer FLAG en +# variable d'environnement / --build-arg (priorite sur ce fichier). +# ===================================================================== + +FLAG=blue{exemple_a_changer} diff --git a/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/flag_config.py b/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/flag_config.py new file mode 100644 index 0000000..894e8af --- /dev/null +++ b/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/flag_config.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ===================================================================== +# Résolution du flag du challenge (rotation hors dépôt public). +# Priorité : variable d'env FLAG > setup/challenge.env > placeholder. +# Le flag réel n'est donc jamais committé (challenge.env est gitignoré). +# ===================================================================== + +import os + +_PLACEHOLDER = "blue{PLACEHOLDER_definir_dans_challenge_env}" + + +def resolve_flag(): + env = os.environ.get("FLAG") + if env: + return env + path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "challenge.env") + if os.path.isfile(path): + with open(path, encoding="utf-8") as f: + for line in f: + line = line.strip() + if line.startswith("FLAG="): + return line[len("FLAG="):].strip().strip('"').strip("'") + return _PLACEHOLDER diff --git a/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/generate_challenge.py b/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/generate_challenge.py index 6391851..21c2100 100644 --- a/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/generate_challenge.py +++ b/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/generate_challenge.py @@ -26,10 +26,12 @@ import time from datetime import datetime, timedelta +from flag_config import resolve_flag + # ─── Configuration du challenge ─────────────────────────────────────────────── CHALLENGE_CONFIG = { - "flag": "blue{m3m_f0r3ns1cs_v0l4t1l1ty_m4st3r}", + "flag": resolve_flag(), "c2_domain": "c2.darkops-syndicate.net", "c2_ip": "185.141.27.83", "c2_port": 4444, @@ -408,6 +410,13 @@ def main(): dump_path = generate_memory_dump(challenge_dir) generate_hints(challenge_dir) + # Hash SHA256 du flag pour le validateur (jamais le flag en clair). + valout_dir = os.path.join(project_dir, "valout") + os.makedirs(valout_dir, exist_ok=True) + flag_hash = hashlib.sha256(CHALLENGE_CONFIG["flag"].encode()).hexdigest() + with open(os.path.join(valout_dir, "flag.sha256"), "w") as f: + f.write(flag_hash + "\n") + print() print("[✓] Challenge prêt ! Fichiers dans :", challenge_dir) print() diff --git a/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/generate_pcap.py b/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/generate_pcap.py index 37a4ffe..3908225 100644 --- a/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/generate_pcap.py +++ b/challenges/2-Blue-Team-Memory-Forensics-Jakub/setup/generate_pcap.py @@ -31,7 +31,8 @@ GW_MAC = b"\x52\x54\x00\x12\x35\x00" DNS_SERVER = "192.168.1.1" -FLAG = "blue{m3m_f0r3ns1cs_v0l4t1l1ty_m4st3r}" +from flag_config import resolve_flag +FLAG = resolve_flag() # ═══════════════════════════════════════════════════════════════════════════════ diff --git a/challenges/2-Blue-Team-Memory-Forensics-Jakub/solution/SOLUTION.md b/challenges/2-Blue-Team-Memory-Forensics-Jakub/solution/SOLUTION.md index a80fe9a..f017adf 100644 --- a/challenges/2-Blue-Team-Memory-Forensics-Jakub/solution/SOLUTION.md +++ b/challenges/2-Blue-Team-Memory-Forensics-Jakub/solution/SOLUTION.md @@ -1,6 +1,9 @@ # 🔵 Solution Complète – Blue Team Memory Forensics > ⚠️ **SPOILERS** – Ne lisez ceci qu'après avoir tenté le challenge ! +> +> Le **flag** est roté par déploiement (défini dans `setup/challenge.env`, jamais committé) : +> la valeur réelle dépend de l'instance déployée. Les autres réponses sont fixes. --- @@ -13,7 +16,7 @@ | **IP du C2** | `185.141.27.83` | | **Domaine C2** | `c2.darkops-syndicate.net` | | **Port C2** | `4444` | -| **Flag** | `blue{m3m_f0r3ns1cs_v0l4t1l1ty_m4st3r}` | +| **Flag** | `blue{…roté, défini dans setup/challenge.env…}` | --- @@ -171,7 +174,7 @@ C2_FALLBACK=185.141.27.83:8443 BEACON_SLEEP=30 XOR_KEY=X0rK3y!@#2026 EXFIL_TAG=CONFIDENTIAL_PROJECT_OMEGA_FILES -FLAG=blue{m3m_f0r3ns1cs_v0l4t1l1ty_m4st3r} +FLAG=blue{…roté, défini dans setup/challenge.env…} ``` **Chaînes SUSPECTES :** @@ -185,7 +188,7 @@ Host: c2.darkops-syndicate.net powershell -ep bypass -nop -w hidden -c "IEX(...)" ``` -→ **FLAG TROUVÉ : `blue{m3m_f0r3ns1cs_v0l4t1l1ty_m4st3r}`** +→ **FLAG TROUVÉ : `blue{…roté, défini dans setup/challenge.env…}`** --- diff --git a/challenges/2-Blue-Team-Memory-Forensics-Jakub/solution/validate_flag.py b/challenges/2-Blue-Team-Memory-Forensics-Jakub/solution/validate_flag.py index 88ed9ae..86b53ab 100644 --- a/challenges/2-Blue-Team-Memory-Forensics-Jakub/solution/validate_flag.py +++ b/challenges/2-Blue-Team-Memory-Forensics-Jakub/solution/validate_flag.py @@ -7,11 +7,21 @@ """ import hashlib +import os import sys # ─── Hashes SHA256 des réponses attendues ───────────────────────────────────── # Les réponses en clair ne sont PAS stockées ici pour éviter le spoil. -# Ces hashes ont été générés lors de la création du challenge. +# Le hash du FLAG est généré au build (rotation via challenge.env) et lu +# depuis flag.sha256 ; les autres réponses sont des faits fixes du challenge. + +def _load_flag_hash(): + path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "flag.sha256") + try: + with open(path) as f: + return f.read().strip() + except OSError: + return None EXPECTED_HASHES = { "pid": "890ebdf964b651bc670b2001f32aad8eb1c0e9704f7857b4961fd0a602eed83b", @@ -19,7 +29,7 @@ "c2_ip": "4e0a24277c461cb7a6b904173aabf898ba90cf9f5832a1a54c5258a6ea26c08b", "c2_domain": "b6bd3f76cc36838799e6969e675143ffa8a738743b613a8e67400cf255d11163", "c2_port": "79f06f8fde333461739f220090a23cb2a79f6d714bee100d0e4b4af249294619", - "flag": "adbace5b3008f4f51eb858fa0ff6ac58923b88da172a4f86902079bb9469abef", + "flag": _load_flag_hash(), } SCORING = {