Self-hosted, browser-based workbench for solving CTFs —
by hand, automatically, or driven by an AI agent.
One ./ctf up, one dashboard, every tool with a web GUI.
CTF Toolbox is a Docker Compose stack with the tools people actually use during a Capture The Flag — steganalysis, forensics, encoding/crypto, web, OSINT, reversing, password cracking — each with a web interface, behind a dashboard that lists them by phase of work.
At its core is StegSuite, an analysis workbench that takes the challenge files, runs 84 analyzers recursively, hunts the flag, cracks what is locked and tells you how it got there. It works per CTF: create the event, drop in its challenges, let the autosolver take the easy ones and keep working on the rest — yourself, or through an AI agent connected over MCP.
- Features · Quick start · How it works
- Working on a CTF · AI agents (MCP) · Tools
- StegSuite · CLI · API · Configuration
- Security model · Testing against real CTFs
- Troubleshooting · Project layout · Contributing
- Ready on start —
./ctf upcreates.env, starts the core, waits until StegSuite is healthy and prints where to go. - One CTF = one project — a CTF holds its challenges and its flag format; a flag in that format marks the challenge solved; a scoreboard shows them all.
- Autosolver — analyzes every unsolved challenge, a few at a time.
- 84 analyzers, recursive: metadata, strings/encodings/esolangs, image stego (LSB, steghide, outguess, zsteg…), OCR/QR, audio (spectrogram, morse, DTMF, SSTV, WAV LSB/levels), network (tshark, TLS with keys/keylog, streams, DNS tunnels), archives/office/email/git, ELF.
- Cracking built in — ZIP (ZipCrypto + AES via hashcat), PDF, steghide,
known-plaintext (
bkcrack), wordlists smallest → largest, rules and masks. - Explains the solution — every flag comes with the chain of files that led to it and the tool output that shows it; Markdown report (EN/IT) and an AI transcript to continue by hand.
- API-first — everything the GUI does is a REST endpoint; the same surface is an MCP server for AI agents.
- Isolated — everything on
127.0.0.1, ports19000+from.env, heavy tools behind Compose profiles, the reversing sandbox has no internet.
git clone https://github.com/dexRand/ctf-docker.git
cd ctf-docker
./ctf up[*] Attendo che StegSuite sia pronto.... ok
Pronto. Da qui:
Dashboard http://localhost:19001/
StegSuite http://localhost:19014/ crea una CTF e trascina gli allegati
API http://localhost:19014/api/docs
IA (MCP) ./ctf mcp
The first start builds StegSuite once (a few minutes). Add tools with profiles,
e.g. ./ctf up web rev (ZAP + reversing sandbox). ./ctf urls lists everything.
flowchart LR
you([You]) -->|browser| dash[Homepage dashboard]
you -->|browser| ss[StegSuite GUI]
agent([AI agent]) -->|MCP| api
cli([./ctf scan / scripts]) -->|REST| api
ss --> api[StegSuite API]
api --> pipe[analysis pipeline<br/>84 analyzers · flag hunt · cracking]
pipe --> data[(./data<br/>projects · files · reports)]
api --> svc[service catalog]
svc -.-> tools[CyberChef · ZAP · Wireshark · SageMath ·<br/>SpiderFoot · Whisper · rev sandbox · …]
StegSuite walks the file tree in discovery order: for each file it runs the analyzers that apply (by type, not by name), stores their output, hunts flags in it, adds extracted files as children and recurses. In auto mode it then cracks locked files with the small wordlists and analyzes what they unlock.
- Create the CTF on the StegSuite home: name + flag format
(
DUCTF{...}, or a regex such asFLAG-[0-9a-f]{8}). - Add challenges: one upload (one or more files) = one challenge, with an optional category. It is analyzed right away.
- Autosolve: one click analyzes every open challenge, 2 at a time. A flag in the CTF's format marks the challenge ✓ solved.
- Keep going on the open ones: the challenge page shows the file tree, every tool's output, locked files (choose wordlists, rules, mask, or a known plaintext), an embedded terminal in the project folder, the flag route graph, the report and the AI transcript. Re-run any tool on any file, with a password if you found one.
Without a CTF, the home page still takes files and analyzes them as a single
project (--auto runs everything, --check waits for you before cracking).
StegSuite is an MCP server at
/api/v1/mcp (Streamable HTTP). With Claude Code:
./ctf mcp # prints the exact command, e.g.
claude mcp add --transport http ctf http://127.0.0.1:19014/api/v1/mcpThe agent gets 19 tools: create/list CTFs, scoreboard, add challenges (files
by URL or base64), autosolve, analyze, status, flags with their route, report,
transcript, read a file, run analyzers on a file, locked files, crack,
wordlists, edit a challenge, and the list of the other toolbox services
(toolbox_services) with their URLs and whether they are up. Typical loop:
create_ctf → add_challenge(start) → project_status … → get_flags
↘ no flag: get_transcript → run_tools / crack / read_file
| Phase | Tool | URL | What it is for | Profile |
|---|---|---|---|---|
| Dashboard | Homepage | http://localhost:19001 | Every tool, grouped by phase, with live status | core |
| Analysis | StegSuite | http://localhost:19014 | CTFs/challenges, 84 analyzers, flag hunt, cracking, reports, REST + MCP | core |
| Analysis | Terminal | http://localhost:19022 | VS Code in the browser (code-server, suite theme) on ./data/projects: file tree, hex editor, PDF/SQLite viewers, asm/smali/solidity syntax, terminals stegsuite (every tool, cdp <project>) and rev (sandbox) · Ctrl+` terminal |
core |
| Analysis | FileBrowser | http://localhost:19012 | Browse ./data: projects, extracted files, reports |
core |
| Crypto/Encoding | CyberChef | http://localhost:19002 | Base64, XOR, RSA, hashes, JWT and much more | core |
| Web | mitmproxy | http://localhost:19003 | Intercept and rewrite HTTP(S) · proxy on :19004 |
core |
| Utility | IT-Tools | http://localhost:19011 | Encoders, converters, hashes, regex | core |
| Recon | Dork generator | http://localhost:19019 | Curated dorks + the Google Hacking Database, from a target (no scraping) | core |
| Web | OWASP ZAP | http://localhost:19005/zap | Web scanner with in-browser GUI · proxy on :19006 · extra add-ons |
web |
| Recon | SpiderFoot | http://localhost:19007/spiderfoot/ | OSINT automation | recon |
| Crypto | SageMath | http://localhost:19010 | Jupyter notebooks for crypto and math | crypto |
| Forensics | Wireshark | http://localhost:19008 | Packet analysis with a web GUI | forensics |
| Cracking | Hashtopolis | http://localhost:19016 | Distributed hashcat (agents via voucher) | crack |
| Audio | Whisper-WebUI | http://localhost:19017 | Speech-to-text with timestamps (SRT/VTT) | audio |
| Rev/Pwn | rev sandbox | http://localhost:19018 | Web terminal for ELF/PE/APK: Ghidra headless, radare2, gdb, strace/ltrace, upx, apktool, jadx, pwntools, qemu-user · no internet | rev |
| Rev/Pwn | rev workbench | http://localhost:19020 | Browser workbench: auto report (rev-report), Unicorn emulation + brute (emulate), angr symbolic execution |
rev |
Every upstream image is pinned by digest (tag@sha256:…), so a new machine
runs exactly what was tested; python3 tools/pin_images.py shows newer builds
and --write updates them. Three images are built locally: StegSuite
(app/Dockerfile), ZAP with extra add-ons (zap/Dockerfile) and the rev
sandbox (rev/Dockerfile).
| Area | Analyzers |
|---|---|
| Identify / metadata | file, exiftool, identify, ffprobe, pdfinfo, pdfid |
| Text & encodings | strings, markup-text (SVG/HTML/XML text nodes), xxd/hexyl/hexdump, decode (base64/32/58/62/85/91/45, hex, binary, rot13 + Caesar/Atbash with a known prefix, A1Z26, URL, quoted-printable, uuencode, Brainfuck/Ook!, high-bit, nested), blobs (base64/hex blocks → files/text), pdftotext, morse-text |
| PDF & keyboards | pdf (encrypted → auto-crack), pdf-images (embedded pictures → OCR), pdf-attachments (embedded files), usb-hid (HID keyboard reports → typed text) |
| Text stego & esolangs | zero-width (ZW chars, two-symbol whitespace, trailing spaces), whitespace (interpreter), case-bits (binary + Bacon), homoglyph, chef (recipes with unknown quantities), sqlite (table text: history, cookies, app data) |
| Image stego | zsteg, lsb-carve, lsb-bits (raw-byte LSB), msb-bits (MSB plane), stepic, psimage, steghide, outguess, jsteg, openstego, png-chunks, png-idat (zstd/deflate IDAT), png-pixels, pixel-bytes (pixels that are another file, chains followed), bit-planes, color-map (few-colour images → distinct colours + index grey), faint (semi-transparent overlays: high-pass + OCR), channel-remap (+ palette-index repaint), image-enhance |
| Vision | ocr (tesseract, EN + IT fallback, denoise + O/0 fixes), qr, gif-frames (frames, diffs, delay bits) |
| Audio / radio | spectrogram, waveform, wav-lsb, wav-levels, morse, dtmf, sstv (Scottie S1/S2), eas (SAME/EAS + AFSK via multimon-ng) |
| Network | pcap (protocols, HTTP/DNS fields, comments, reassembled UDP/TCP streams, base64 split one token per packet, text in source ports, DNS tunnels, DNS tunnels per destination server, evil bit RFC 3514, HTTP objects, TLS with a key or a TLS 1.3 keylog, HTTP/2, query parameters leaked one per request (hex/base64/XOR), Wi-Fi SSIDs decoded), usb-hid (keyboard reports), wpa (handshake to hashcat 22000) |
| Archives & documents | 7z, nested-archive, tar-chain (chains of nested tars, in memory), binwalk (scan/extract), foremost, office (OOXML/ODF + macros), olevba (VBA source + decoded strings), eml, git (history, reflog, every object), disk (sleuthkit: partitions, fls/icat), android-boot (boot.img kernel + ramdisk cpio), ds-store (macOS folder file names, deleted too), openssl (Salted__ + sibling password), cryptojs (U2FsdGVkX1… + the strings of the same file) |
| Windows | evtx (event logs as text, base64 pieces joined into the flag), sam (local hashes + hashcat), winreg (Run/RecentDocs/TypedPaths/UserAssist from a hive), firefox-logins (saved logins via libnss3), GPP cpassword decrypted (in markup-text) |
| Repair | pngcheck, png-repair (chunks), image-repair (JPEG/BMP/GIF headers, junk prefix, BMP height), jpeg-dqt (wiped quantization tables), byte-swap (16/32-bit word-swapped files) |
| Binaries | elf (sections, dynamic info, checksec), readelf, objdump |
Flag hunt — every tool output is scanned in several views (raw, UTF-16,
URL-decoded, whitespace-collapsed, one level of inline base64/hex, XOR of
string pairs) for the common CTF prefixes (picoCTF, flag, CTF, HTB,
DUCTF, ictf, tjctf, lactf, SEKAI, …), rot13 twins, OCR-damaged
braces, the CTF's own format and an optional global FLAG_PATTERN.
Duplicates, rot13 twins and fragments of a longer flag collapse into one.
Cracking — fcrackzip, hashcat (zip2hashcat, ZipCrypto + AES, rules,
masks), pdfcrack, stegseek, bkcrack (known plaintext). Wordlists from
./wordlists (mounted), uploads (persisted) and the full rockyou.txt in the
image, tried smallest → largest. Auto mode only uses lists ≤ AUTO_CRACK_MAX_MB
within AUTO_CRACK_BUDGET_S, so a false "locked" can't stall a challenge.
A password hint in a sibling file (psw: …) and the descriptive metadata of the challenge (EXIF/XMP Title, Comment, Author…, PNG text chunks) are tried first, automatically.
GUI — terminal-themed, EN/IT, file tree, per-tool output with ANSI colours, image/audio/video previews, live log (WebSocket), embedded terminal (xterm.js), force-directed graph of the flag route, report and transcript, responsive.
Three ways into the same isolated ELF/PE/APK sandbox (profile rev):
- terminal (
:19018) — plain web shell with the tool menu (menu), no internet - workbench (
:19020) — upload a binary and it runsrev-reportautomatically; emulate a function with Unicorn (also brute-forcing the first argument) or solve it with angr - VS Code (
:19022,term) — itsrevterminal profile runsdocker execinto this sandbox; panes and tabs come from VS Code
./ctf up [profile…] # core (+ profiles), waits until ready
./ctf up-all # every profile (several GB of images)
./ctf down | status | logs <service> | list
./ctf urls # every service and its URL (ports from .env)
./ctf open <service> # open in the browser (stegsuite, zap, rev, wireshark…)
./ctf mcp # connect an AI agent
./ctf pull <url>… # download challenge attachments into ./data/pull
./ctf scan <file>… # analyze with StegSuite, print flags (exit 1 on error)
./ctf reports # browse ./data in FileBrowser
./ctf dork [target] # dork generator (--update syncs the GHDB)
./ctf geo <photo>… # photo-geolocate: EXIF/GPS, sun position, OCR, map links
./ctf test [-j 3] # regression + real CTF challenges (see below)
./ctf ui-smoke # headless-browser smoke test of the SPAOpenAPI/Swagger at http://localhost:19014/api/docs; the guide with examples
is docs/API.md. Highlights:
| CTFs | GET/POST /ctfs · GET/PATCH/DELETE /ctfs/{cid} · POST /ctfs/{cid}/challenges · POST/GET/DELETE /ctfs/{cid}/autosolve |
| Challenges | POST /projects · GET/PATCH/DELETE /projects/{pid} · `POST /projects/{pid}/start |
| Results | /findings · /runs · /events · /flags (chain + evidence) · /summary · `/report?format=md |
| Work | POST /projects/{pid}/files/{fid}/run (re-run tools, optional password) · /locked · POST /crack (queued per file) · GET /cracks · DELETE /cracks/{id} · /wordlists |
| Stateless | GET /tools · POST /tools/{tool} (one tool on one file) |
| Toolbox | GET /services (other tools: URL + up) · POST /mcp |
A stdlib client lives in tools/stegsuite_client.py.
Everything comes from .env (created from .env.example on first run).
| Variable | Purpose |
|---|---|
BIND_ADDR |
Interface every port is published on — default 127.0.0.1 (local only) |
*_PORT |
Host port of each service (19000+); links, dashboard and API follow them |
STEGSUITE_API_KEY |
Require a key on the API/MCP (header, cookie or ?key=) |
FLAG_PATTERN |
Extra global flag regex (a CTF's own format is set per CTF) |
MAX_DEPTH, HEAVY_TOOLS, TOOL_WORKERS, MAX_UPLOAD_MB |
Recursion depth, concurrent heavy tools, analyzers run in parallel per file, upload cap |
AUTO_CRACK_MAX_MB, AUTO_CRACK_BUDGET_S, CRACK_BUDGET_S, HASHCAT_RULES |
Cracking limits and rules |
CRACK_WORKERS |
Manual cracks run at the same time; the rest wait in the crack queue |
OCR_LANGS, OCR_EXTRA_LANGS, OCR_MAX_DIM |
OCR languages and downscaling |
RETENTION_DAYS, RATE_LIMIT_RPM, RATE_LIMIT_BURST |
Auto-delete old projects, per-IP rate limit |
MITMWEB_PASSWORD, HASHTOPOLIS_*, PUID/PGID/TZ |
Other services |
Memory: the core runs comfortably with ~4 GB free. The mem_limit caps add up
to ~6 GB for the core and ~25 GB with every profile (whisper 6 GB, rev and
StegSuite 4 GB each), so start profiles as you need them.
- Local by default: every port is published on
127.0.0.1(BIND_ADDR). Several tools (ZAP, Wireshark, mitmweb, the dashboard with the Docker socket) have no strong authentication: expose them only on a network you trust. - Untrusted files: analyzers run inside the StegSuite container with timeouts that kill the whole process group; git repositories from archives run with their config neutralised; the report renderer escapes everything and keeps only http(s)/same-origin links.
- Running binaries: only in the rev sandbox — internal network without
egress,
cap_drop: ALL(+SYS_PTRACE),no-new-privileges, pid/memory limits. - API key (optional) protects REST, WebSockets and MCP; secrets stay in
.env(git-ignored) and StegSuite only receives the variables it needs.
Every change is checked against real challenges, not only unit tests:
app/tests/run_unit.sh # 340+ pytest tests inside the StegSuite image
./ctf test -j 3 # 33 fixture cases + 35 real challenges + writeup check
./ctf test -j 3 --extra # + 45 more real challenges (picoCTF, TJCTF, DUCTF, ImaginaryCTF, WaniCTF, TCP1P…)
./ctf test --only-new --gaps # known gaps: the backlog of techniques to add
./ctf lint # static analysis in containers: ruff, bandit, vulture, jscpdReal challenges are downloaded from public archives/write-ups (picoCTF,
OliCyber/ITS, SekaiCTF, DownUnderCTF, TJCTF, ImaginaryCTF, LA CTF, UIUCTF…)
with their documented flags — see docs/CHALLENGES.md.
--json-out records per-challenge timings to compare performance.
| Symptom | Fix |
|---|---|
./ctf up waits and StegSuite never answers |
./ctf logs stegsuite; first build can take minutes |
| A tool shows down on the dashboard | its profile is not started: ./ctf up <profile> |
| Port already in use | change that *_PORT in .env; links follow automatically |
401 from the API |
STEGSUITE_API_KEY is set: the GUI asks once; scripts read it from .env |
| SageMath asks for a token | ./ctf logs sagemath |
| mitmweb login | password MITMWEB_PASSWORD (default mitm) |
| Out of memory | stop heavy profiles (whisper, rev, forensics), lower HEAVY_TOOLS |
compose.yaml services (project "ctf"), profiles, x-base anchor
ctf CLI wrapper
.env.example ports and configuration
app/ StegSuite: backend/ (FastAPI), frontend/ (Vue 3), tests/, Dockerfile
backend/analyzers/ one module per analyzer + util.py (shared helpers)
backend/ pipeline, jobs, flaghunt, crackflow, report, ctfs, mcp_server, api/
config/ homepage dashboard, dork generator, filebrowser, whisper, zap
rev/ reversing sandbox image (ttyd + Ghidra headless + tools) + rev-report/emulate/angr and the web workbench
zap/ ZAP image with extra add-ons
tools/ stegsuite_client, scan, check_consistency, dork, photo-geolocate
osint/ separate OSINT area (compose override, own README)
docs/ API.md, ADDING-A-TOOL.md, CHALLENGES.md, screenshots
tasks/ plans and status
- New analyzer:
docs/ADDING-A-TOOL.md— one module inapp/backend/analyzers/, shared helpers fromutil.py, a unit test, and if possible a real challenge inapp/tests/real_challenges.py. - New service: update
compose.yaml,.env.example, the dashboard (config/homepage/services.yaml), theSERVICEStable inctf, the catalog inapp/backend/services.pyand this README —python3 tools/check_consistency.pytells you what is missing (CI runs it). - Conventions and rules for agents:
AGENTS.md.
MIT — see LICENSE. The Agent Skills under .opencode/skills/ come
from addyosmani/agent-skills
(MIT, see .opencode/ATTRIBUTION.md);
third_party/zip2hashcat.py keeps its own MIT notice.

