Security Azure

CTFd on Ubuntu 24.04 on Azure User Guide

| Product: CTFd on Ubuntu 24.04 LTS on Azure

Overview

CTFd is the popular open source Capture The Flag competition platform used to run security training events, workshops and jeopardy style competitions. It provides a challenge board, a live scoreboard, individual or team play, solve tracking, hints and a complete administration panel for authoring challenges, flags and pages. The cloudimg image runs CTFd 3.8.6 in a dedicated Python virtual environment served by gunicorn with gevent workers, bound to loopback and reverse proxied behind nginx, with MySQL 8.0 for durable state and Redis for caching. The first run setup is completed automatically, a demo challenge is seeded so the board is a real event, and the MySQL root, application database, Flask secret key and CTFd administrator passwords are all rotated per instance at first boot, so nothing ships with a known password.

What is included:

  • CTFd 3.8.6 from the official release at /opt/CTFd, served by gunicorn (gevent, 2 workers)
  • nginx reverse proxy on port 80 with an unauthenticated static /healthz for load balancer probes
  • MySQL 8.0.x holding the CTFd database (ctfd) with a per VM application user
  • Redis for CTFd caching and sessions, bound to loopback
  • A single admin account created automatically at first boot, with a per VM password
  • A seeded demo challenge so the challenge board and scoreboard are populated on first boot
  • ctfd-firstboot.service rotating every secret, completing setup and seeding the demo challenge
  • Host agnostic URLs: CTFd renders on the VM public IP, on 127.0.0.1, or on any domain you point at it
  • CTFd, nginx, MySQL and Redis enabled and auto starting on boot
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key, and a VNet with a subnet. Standard_B2s (2 vCPU, 4 GB RAM) is a comfortable starting point for a small event; move up to a D2s or D4s size for larger competitions with many concurrent players. Open inbound ports 80 and 443 (and 22 to your management network) on the network security group. MySQL, Redis and the gunicorn app all listen only on loopback and are never exposed.

Step 1: Deploy and connect

Launch the image from the Azure Marketplace, then connect over SSH as azureuser:

ssh azureuser@<vm-ip>

The first boot service rotates the credentials, completes the CTFd setup wizard and seeds the demo challenge within a few seconds of the instance starting.

Step 2: Verify the services

Confirm CTFd, nginx, MySQL and Redis are active. CTFd, MySQL and Redis are bound to loopback only; nginx is the single ingress on port 80:

sudo systemctl is-active ctfd.service nginx.service mysql.service redis-server.service
sudo ss -ltn | grep -E ':80|:8000|:6379|:3306'

CTFd, nginx, MySQL and Redis active, with CTFd bound to loopback on port 8000

Step 3: Check the versions

CTFd reports its version from the installed package; the runtime is gunicorn on MySQL and Redis:

sudo -u ctfd bash -c "cd /opt/CTFd && /opt/CTFd/venv/bin/python -c 'import CTFd; print(\"CTFd\", CTFd.__version__)'"
/opt/CTFd/venv/bin/gunicorn --version
mysql --version
redis-server --version | awk '{print $1, $3}'

CTFd 3.8.6, gunicorn, MySQL 8.0 and Redis version strings

Step 4: Inspect the database

The schema is created and a single administrator account exists, stored with a hashed password, so there is no default or shared login. The seeded demo challenge is present too. Read the application database password from the root only credentials file, then inspect the tables, users and challenges:

DB_PASS=$(sudo grep '^CTFD_DB_PASSWORD=' /root/ctfd-credentials.txt | cut -d= -f2-)
sudo mysql -uctfd -p"$DB_PASS" ctfd -e "SELECT COUNT(*) AS ctfd_tables FROM information_schema.tables WHERE table_schema='ctfd'"
sudo mysql -uctfd -p"$DB_PASS" ctfd -e "SELECT id,name,type,hidden FROM users"
sudo mysql -uctfd -p"$DB_PASS" ctfd -e "SELECT id,name,category,value,state FROM challenges"

The CTFd schema table count, the single admin user, and the seeded demo challenge

Step 5: Read the administrator password

The per VM credentials are written to a root only file. Read it to pick up the CTFD_ADMIN_USER and CTFD_ADMIN_PASSWORD for signing in:

sudo cat /root/ctfd-credentials.txt

The per VM credentials file with the administrator user and rotated passwords

Keep this file secure. Change the administrator password from within CTFd once you have signed in for the first time. The Flask secret key lives in /etc/ctfd/ctfd.env.

Step 6: Sign in

Confirm CTFd is serving locally, then browse to http://<vm-ip>/login in your browser and sign in as admin with the password from Step 5:

curl -s -o /dev/null -w 'home    : HTTP %{http_code}\n' http://127.0.0.1/
curl -s -o /dev/null -w 'healthz : HTTP %{http_code}\n' http://127.0.0.1/healthz

The CTFd sign in page

Step 7: The challenge board

After signing in you land on the challenge board. Each card is a challenge; the seeded Welcome challenge is worth 100 points. Click a challenge to open it, read the prompt, and submit a flag in the flag{...} format. Authoring more challenges is done from the admin panel in Step 9.

The CTFd challenge board showing the seeded Welcome challenge

Step 8: The scoreboard

The scoreboard ranks players (or teams) by score and shows a timeline of solves as the event runs. It is public by default so spectators can follow along, and updates live as flags are submitted.

The CTFd live scoreboard

Step 9: The admin panel

Open the admin panel to run your event: create and edit challenges, add flags and hints, manage users and teams, control challenge and scoreboard visibility, set start and end times, and watch statistics. This is where you build out the competition beyond the seeded demo challenge.

The CTFd admin panel managing challenges

Step 10: Components

Component Path
CTFd install /opt/CTFd/
Python venv /opt/CTFd/venv/
Environment (secret key, DB URL) /etc/ctfd/ctfd.env (mode 0640 root:ctfd)
gunicorn unit /etc/systemd/system/ctfd.service
nginx vhost /etc/nginx/sites-available/cloudimg-ctfd
Firstboot script /usr/local/sbin/ctfd-firstboot.sh
MySQL data /var/lib/mysql/
Credentials /root/ctfd-credentials.txt (mode 0600 root:root)

Step 11: Security and next steps

  • Change the administrator password from within CTFd (Admin, then Config, then your account) after your first sign in, and add real accounts for your players.
  • Enable HTTPS by installing certbot and requesting a certificate: sudo apt-get install -y certbot python3-certbot-nginx then sudo certbot --nginx.
  • Restrict the network security group so that ports 80 and 443 are open only to your players or load balancer, and port 22 only to your management network.
  • Author your challenges in the admin panel, set the event start and end times, and choose challenge, account and score visibility to match how you want to run the competition.
  • Point your domain at the instance; because CTFd derives its URLs from the request host, no rewrite is needed.
  • Patch the OS regularly with sudo apt-get update and sudo apt-get upgrade.

Licensing

CTFd is distributed under the Apache-2.0 licence and is free to use commercially. cloudimg packages and supports the image; support is available 24/7 at support@cloudimg.co.uk. All product and company names are trademarks or registered trademarks of their respective holders and their use does not imply affiliation or endorsement.