Build and deploy hands-on training labs from any topic. Students get a browser, a live terminal, and real targets. You get a scoreboard, auto-unlocking challenges, and a single-command deploy.
Students walk in, open a browser, and get dropped into a live terminal. Their first instinct: scan the network. They find hosts, but everything's locked down — filtered ports, hidden challenges. They can see the scoreboard but can't touch it. Curiosity is lit.
The instructor walks through the presentation — slides are live on everyone's phone. As each section wraps, the next set of challenges auto-unlocks based on what the class has already solved. No awkward "okay now try this" transitions — the lab progresses itself.
First blood gets confetti on the scoreboard. Teams race through challenges with real targets and real tools. Everything runs from a single command. Tear it down in one command. Redeploy in five minutes. Zero install on student machines. Just a browser.
Shell-in-a-Box: each student gets their own isolated container with a web terminal. No SSH keys, no client install. One tab per student.
Flask-based progress tracker. Flag submission, 3-tier hints (Nudge/Guide/Walkthrough), auto-unlock by progression. First blood confetti.
Isolated Docker networks. Each target bridges two subnets, forcing lateral movement. Decoy services on every host.
Validates Docker, Compose, ports, disk, memory. Calculates resource budget per student count. Constructive failure messages.
git clone https://github.com/mwilco03/FOC.git cd FOC # Run preflight ./platform/scripts/preflight.sh # Deploy the pivoting lab cd courses/pivoting docker compose -f compose.yml up -d # Access: # Student Terminal: http://localhost:4200 # Scoreboard: http://localhost:8080
# From courses/pivoting/ docker compose -f compose.yml down -v
../../platform/scripts/reset.sh
A course is a directory under courses/. It must be independently deployable with no dependency on sibling courses.
courses/your-course/
compose.yml # References ../../platform/ for shared infra
.env.template # Environment variable template
targets/ # Course-specific Docker containers
service-a/
Dockerfile
entrypoint.sh
service-b/
Dockerfile
entrypoint.sh
hints/ # 3-tier hint files per challenge
challenge-01.json # { "nudge": "...", "guide": "...", "walkthrough": "..." }
tools/ # Static binaries students may need
slides/ # Instructor presentation content
instructor/ # Solution guide (password-locked)
README.md # What this course covers, how to run it
Give the platform a topic — it derives the curriculum, generates challenges, builds slide content, and emits the Docker infrastructure. No pre-made PowerPoint required.
# Input: a topic Topic: "Network Reconnaissance with Nmap" # Output: everything courses/nmap-recon/ compose.yml # 13 target services + student terminals + scoreboard targets/ # Dockerfiles for each vulnerable service hints/ # 3-tier hints per challenge slides/ # Reveal.js slide deck (maps 1:1 to challenge categories) instructor/ # Solution guide + pace guide .env.template # Flags, passwords, student count
The .pptx is an output, not an input. Slides and challenges are co-derived from the same topic outline, so they stay in sync automatically.
| Mode | Use Case | Resource Cost | Attack Surface |
|---|---|---|---|
shellinabox |
Terminal-only labs (default) | ~128MB/student | Minimal |
guacamole |
Labs needing GUI (Wireshark, browser, forensics desktop) | ~576MB/student | Slightly larger |
Guacamole runs one guacd per student (not shared), matching Shell-in-a-Box's isolation model. Decision rule: does the lab need a GUI? If no, use Shell-in-a-Box.
# Set in .env or compose override LAB_TERMINAL_MODE=shellinabox # default LAB_TERMINAL_MODE=guacamole # opt-in when GUI needed
Students have passwordless sudo inside containers. The container itself must be a well-sealed box. Defense in depth — each layer is independent.
| Control | What It Prevents |
|---|---|
| Socket proxy (not raw socket mount) | Host takeover via Docker API |
cap_drop: ALL + explicit allowlist | Capability-based escapes |
| Custom seccomp profile | Dangerous syscalls (unshare, mount, ptrace) |
no-new-privileges: true | SUID/SGID privilege escalation |
read_only: true + tmpfs | Persistent malicious writes |
pids_limit | Fork bombs |
mem_limit + cpus | Resource exhaustion DoS |
userns-remap on daemon | Host impact if escape succeeds |
internal: true on isolated nets | Lateral movement to host/internet |
privileged: false everywhere | Full host access |
# In compose.yml
student-terminal:
cap_drop:
- ALL
cap_add:
- NET_RAW # nmap SYN scans, ping
- NET_BIND_SERVICE # tools needing ports <1024
- SETUID
- SETGID
- DAC_OVERRIDE # sudo mechanics
security_opt:
- no-new-privileges:true
- seccomp:../../platform/seccomp/student-terminal.json
read_only: true
tmpfs:
- /tmp:size=64m,noexec
- /run:size=16m
- /home:size=128m
pids_limit: 200
mem_limit: 512m
cpus: "0.5"
Run the lab under a dedicated system account. Isolates .env (flags + passwords) from the instructor's home directory. If a student escapes, they land as ctflab, not the instructor.
# Linux useradd --system --create-home --shell /bin/bash ctflab usermod -aG docker ctflab chown -R ctflab:ctflab /path/to/FOC sudo -u ctflab ./platform/scripts/start.sh
Before any docker build, the preflight script calculates whether the host can handle the lab at the specified student count.
STUDENT_COUNT=10 LAB_TERMINAL_MODE=shellinabox Fixed infrastructure: ~1,672 MB Per-student (x10): ~1,280 MB Estimated total: ~2,952 MB Recommended (30% headroom): ~3,837 MB
Three tiers of output:
# Example failure output: Insufficient resources. Options: 1. Reduce student count: STUDENT_COUNT=6 (saves 512 MB) 2. Switch terminal mode: LAB_TERMINAL_MODE=shellinabox (saves 4,480 MB) 3. Use lighter desktop: DESKTOP=i3 (saves 3,840 MB) 4. Reduce target hosts: TARGET_COUNT=8 (saves 320 MB)
9-hop progressive CTF across 10 isolated networks. SSH tunneling, FTP credential harvesting, DNS zone transfers, Redis RCE, SMB enumeration. 4,050 total points. 3-tier hints. Randomized flags and IPs per session.
Status: ~65% complete
More labs are generated from topics using the skill system. Each lab lands as a new directory under courses/.