Skip to content

rootHytx/NervCTF

Repository files navigation

NervCTF

CLI + server toolchain for managing CTFd competitions: deploy challenges from YAML, provision ephemeral per-team containers, and detect flag sharing — all from one command.


Quick Start

1. Download

Grab nervctf (CLI) and remote-monitor (server) from the releases page. Place both somewhere in your PATH.

2. Prepare challenges

challenges/
├── web/sqli/challenge.yml
├── pwn/overflow/challenge.yml
└── misc/trivia/challenge.yml

Minimal challenge.yml:

name: My Challenge
category: web
value: 100
type: standard
flags:
  - flag{example}

3. Setup server

nervctf setup

Prompts for target IP, SSH user, and CTFd path. Deploys Docker, CTFd, the monitor service, and the CTFd plugin via an embedded Ansible playbook. Creates .nervctf.yml with all settings.

4. Deploy

nervctf deploy

Validates, diffs, and pushes your challenges to CTFd. Run --dry-run to preview changes.


Installation

Pre-compiled binaries (recommended)

Download from GitHub Releases. You need:

  • nervctf — for your local machine (Linux x86_64, ARM64, Windows, macOS)
  • remote-monitor — for the CTFd server (Linux x86_64 only)

Build from source

# With Nix (provides all deps)
nix develop .# --command cargo build --release

# Without Nix (Debian/Ubuntu)
sudo apt install build-essential pkg-config libssl-dev
cargo build --release

Cross-compilation: make release-musl (static), make release-arm64, make release-windows. Run make help for all targets.


Configuration

.nervctf.yml

Created by nervctf setup. Searched upward from --challenges-dir.

# CTFd/monitor host
monitor_ip: 1.2.3.4
monitor_port: 33133             # default: 33133

# Authentication
monitor_token: <auto-generated> # written by nervctf setup

# Deployment credentials (used by nervctf setup / setup --upgrade)
monitor_user: root              # SSH user on the CTFd host
monitor_ctfd_path: /home/admin/CTFd  # defaults to /home/<monitor_user>/CTFd
ssh_key_path: ~/.ssh/id_rsa.pub

# Local challenge directory
challenges_path: ./challenges

# Tuning (optional)
# NOTE: max_concurrent_provisions and max_instances_per_team are baked into the
# remote-monitor's environment at setup time. Changing them here has no effect
# until you run nervctf setup --upgrade again.
max_concurrent_provisions: 4    # parallel Docker builds/provisions
max_instances_per_team: 3       # 0 = unlimited

# CTFd public domain shown in admin dashboard links (defaults to monitor_ip)
ctfd_domain: ctfd.example.com

# Split-machine mode (optional — run containers on a separate node)
runner_ip: 192.168.1.50
runner_user: docker
runner_domain: challenges.example.com  # optional — shown to players instead of runner_ip

Priority (highest wins): CLI flags → environment variables → .nervctf.yml

CLI flag Env var Description
--monitor-url MONITOR_URL Remote monitor URL
--monitor-token MONITOR_TOKEN Monitor auth token

Commands

Command Description
nervctf setup Provision server (Docker, CTFd, plugin, monitor)
nervctf setup --upgrade Push updated plugin + binary, restart services
nervctf deploy Create/update changed challenges
nervctf deploy --dry-run Preview diff without changes
nervctf deploy --recreate Force re-deploy all (rebuild images, re-sync files)
nervctf validate Check challenges for errors
nervctf validate --debug Full field-by-field view
nervctf list List local challenges
nervctf scan Scan + print statistics
nervctf fix Interactively patch missing YAML fields

Challenge Specification

Full example with all supported fields:

name: Advanced Challenge
version: "0.3"
category: web
description: Find the vulnerability.
value: 300
type: standard            # standard | dynamic | instance
state: visible
connection_info: "nc challenge.example.com 1337"
attempts: 5
topics: [web, owasp-top-10]   # optional freeform topic labels

flags:
  - flag{simple}
  - type: static
    content: "flag{alt}"
    data: case_insensitive

tags: [web, sql-injection]
hints:
  - "Free hint"
  - content: "Paid hint"
    cost: 50

files:
  - dist/source.py

requirements:
  - "Warmup"

next: "Follow-up Challenge"

Dynamic scoring

type: dynamic
value: 0
extra:
  initial: 500
  decay: 50
  minimum: 100
  decay_function: linear    # linear (default) | logarithmic

Instance challenges

type: instance provisions ephemeral containers per team. See docs/instance-challenges.md for the full reference.

type: instance
value: 0
extra: { initial: 500, decay: 50, minimum: 100 }
instance:
  backend: docker          # docker | compose | lxc
  image: .                 # local path or registry image
  internal_ports: [1337]   # array; multi-port: [80, 443] — each gets a random host port
  connection: nc
  flag_mode: random
  timeout_minutes: 45

Remote Monitor

Runs on the CTFd host. Writes directly to MariaDB (no CTFd API calls), manages instance lifecycle, and serves the admin dashboard.

CLI ──token──▶ remote-monitor:33133 ──SQL──▶ CTFd MariaDB
                     │
               instance manager
          ┌─────────┴─────────┐
       local              split-machine
  (docker daemon)    (SSH to runner node)

Admin dashboard: http://<host>:33133/admin?token=<TOKEN>

See docs/remote-monitor.md for env vars, routes, and API reference.


Troubleshooting

Symptom Fix
No challenges found Files must be <category>/<name>/challenge.yml (max depth 5)
File upload 500 chown -R 1001:1001 <CTFd>/.data/CTFd/uploads
state: Field may not be null Run nervctf fix
Monitor 401 MONITOR_TOKEN mismatch between CLI and server
ansible-playbook not found Run inside nix develop .#

Development

make check    # cargo check
make test     # unit tests
make fmt      # rustfmt

See ARCHITECTURE.md for the full system reference and docs/ for per-module documentation.


License

MIT License. See LICENSE for details.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors