Security AWS

CTFd Capture The Flag Platform on AWS User Guide

| Product: CTFd on AWS

Overview

This image runs CTFd, the popular open source Capture The Flag competition platform used to run security training events, workshops and jeopardy style competitions. From one web interface you get a challenge board, a live scoreboard, individual or team play, solve tracking, hints and a complete administration panel for authoring challenges, flags and pages.

CTFd is a Python and Flask application served by the gunicorn WSGI server with gevent workers, bound to the loopback interface and reverse proxied behind nginx on port 80. Durable state lives in a MySQL 8.0 database and Redis provides caching and sessions, both bound to loopback so only nginx on port 80 is reachable from the network. A static, unauthenticated health endpoint is served for load balancer probes.

The first run setup wizard, which CTFd normally asks you to complete by hand, is completed automatically on the first boot of every deployed instance, so you land directly on a populated challenge board rather than an open setup screen a visitor could hijack. The administrator account, the Flask secret key, the MySQL password and the application database password are all generated freshly on each instance, so two instances launched from the same Amazon Machine Image never share a secret. The administrator credentials are written to /root/ctfd-credentials.txt with mode 0600 so that only the root user can read them.

Prerequisites

Before you deploy this image you need:

  • An Amazon Web Services account where you can launch EC2 instances
  • IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
  • An EC2 key pair in the target Region for SSH access to the instance
  • A VPC and subnet in the target Region, with a security group allowing inbound port 22 from your management network and port 80 for the web interface
  • The AWS CLI (version 2) installed locally if you plan to deploy from the command line

Step 1: Launch the Instance from the AWS Marketplace

Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images choose AWS Marketplace AMIs and search for CTFd. Select the cloudimg listing and choose Select, then Continue on the subscription summary.

Pick an instance type of m5.large or larger. Choose your EC2 key pair under Key pair (login). Under Network settings select your VPC and subnet, and either create or select a security group that opens port 22 from your management network and port 80 for the web interface. Leave the root volume at the default size or larger.

Select Launch instance. First boot initialisation takes a short time after the instance state becomes Running and the status checks pass, while the per instance secrets are generated, the setup wizard is completed and a demo challenge is seeded.

Step 2: Launch the Instance from the AWS CLI

The following block launches an instance from the cloudimg CTFd Marketplace AMI into an existing subnet and security group. Replace <ami-id> with the AMI ID shown on the Marketplace listing, <key-name> with your EC2 key pair name, <subnet-id> with your subnet ID, and <security-group-id> with a security group that opens port 22 from <your-mgmt-cidr> and port 80 for the web interface.

# launch into a subnet whose security group allows SSH from <your-mgmt-cidr> and HTTP from your users
aws ec2 run-instances \
  --image-id <ami-id> \
  --instance-type m5.large \
  --key-name <key-name> \
  --subnet-id <subnet-id> \
  --security-group-ids <security-group-id> \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=ctfd}]'

When the instance reaches the Running state and its status checks pass, note its public IP address or DNS name from the EC2 console or with aws ec2 describe-instances.

Step 3: Connect to Your Instance

Connect over SSH using your key pair and the login user for your operating system variant.

OS variant SSH login user
Ubuntu 24.04 ubuntu
ssh -i <key-name>.pem ubuntu@<public-ip>

Step 4: Retrieve the Administrator Password

The administrator password is unique to your instance and was generated on first boot. Read it as root:

sudo cat /root/ctfd-credentials.txt

The file lists the CTFd URL, the administrator login name (admin) and the generated password, along with the application database name, its user and password, and the MySQL root password. Keep this file somewhere safe.

Step 5: Sign In to CTFd

The web interface is served on port 80 by nginx. In a browser, go to the sign in page:

http://<instance-public-ip>/login

Sign in with login name admin and the password from the credentials file.

The CTFd sign in page

CTFd derives its base URL from the address you reach it on, so it answers correctly whether you browse by public IP, private IP or a custom domain name, and there is no baked in build time address to change.

Step 6: Confirm CTFd Is Running

Over SSH, confirm the application, web server, database and cache are active:

sudo systemctl is-active ctfd nginx mysql redis-server

You should see all four services reported as active. The unauthenticated health endpoint returns 200:

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

The home page renders and the setup wizard is closed, so a visitor can never re-run setup (302 is the expected redirect away from /setup):

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

CTFd's gunicorn workers, the MySQL database and Redis all listen on the loopback interface only and are never exposed directly; only nginx on port 80 is reachable from the network:

sudo ss -tlnH | awk '{print $4}' | sort -u | grep -E '127.0.0.1:(8000|3306|6379)'

Step 7: Author Challenges and Flags

Open Admin Panel from the top navigation to reach the administration area, then Challenges to author your event. The image ships with one demo challenge, Welcome, in the Getting Started category so the board is a real event from the first boot.

The CTFd administration panel

Select + to create a challenge: give it a name, category, description and point value, choose the challenge type (standard or dynamic scoring), then add one or more flags (static or regular expression) that players submit to score. You can attach files, add hints, and set unlock requirements so challenges reveal in sequence. Set a challenge's state to visible to publish it to players.

Step 8: Run the Event

Players browse the challenge board, submit flags and climb the live scoreboard. The board groups challenges by category and shows each challenge's point value.

The CTFd challenge board

Under Admin Panel you control the event: Config sets the CTF name, start and end times, and whether the event is in users or teams mode; Users and Scoreboard manage participants and standings; Submissions and Statistics track solves and progression. In teams mode, players form or join teams and scores aggregate per team.

Step 9: Enable HTTPS

The web interface is served over plain HTTP on port 80 by nginx. For production use, place it behind TLS so that the administrator password and player submissions are encrypted in transit. Point a domain name such as <your-domain> at the instance, then obtain a certificate for that domain, for example with a managed certificate on an Application Load Balancer in front of the instance, or with Certbot on the instance, and configure nginx to listen on 443 with your certificate.

Because CTFd derives its base URL from the request and honours the X-Forwarded-Proto header that nginx sets, no application change is needed when you move to HTTPS: once a certificate is in front of the instance and you browse to the https:// address, CTFd generates its links over HTTPS automatically. Restrict the security group so that ports 80 and 443 are reachable only from the networks that need the web interface.

Step 10: Backup and Maintenance

Back up the application by snapshotting the instance's EBS root volume, which captures the MySQL database, the CTFd application tree and any uploaded challenge files. You can also take a logical backup of the database over the loopback interface using the MySQL root password from the credentials file, writing it to your chosen backup directory <backup-dir>:

DBPASS=$(sudo awk -F= '/^MYSQL_ROOT_PASSWORD=/{print $2}' /root/ctfd-credentials.txt)
sudo mysqldump -uroot -p"$DBPASS" ctfd > <backup-dir>/ctfd-backup.sql

Apply operating system security updates with sudo apt-get update && sudo apt-get upgrade and reboot when a new kernel is installed; CTFd, nginx, MySQL and Redis start automatically on boot.

Support

This image is published and supported by cloudimg. Support covers deployment, challenge and event configuration, teams and users management, theme and plugin questions, version upgrades, performance tuning, the MySQL database, TLS and backups. Contact cloudimg through the support channel listed on the AWS Marketplace listing.