A tiny bit of context

So every now and then I give introductory courses to embedded systems security.  To keep the students engaged, I mix in a CTF: most practical cases are actually challenges that earn points.  At the end of the course, the team that has gained the most points wins a prize. In the first iterations of the course, the students were asked to call me upon so that I could check their answer.  The main issue with this approach is its lack of fairness: if two teams find the right answer at the same time, who wins is arbitrarily determined by which raised hand I saw first. To solve this issue, I decided to deploy a CTF management web portal.  After some research, I decided to use CTFd [1].  The platform is open source, but the developers also offer managed services.  As I have a very low volume of users (between 8 and 15), I decided to self-host it. My usual goto is CentOS 7.0.  However, having never set up a Python Web Server before, I chose to follow the developers recommendations and used Ubuntu 18.04.  Since my VPS does not run on Docker but on LXC, using a prebuilt image was not an option.

Installing CTFd

This is done starting from a fresh, fully updated Ubuntu 18.04.  CTFd will be ran through gunicorn to serve the Python web service using a low privilege user. The CTFd GitHub Wiki actually talks about using uwsgi, but I could not get it to work properly (challenge submissions failed) and the developers recommended that I use gunicorn instead. Install the dependencies:
# apt install git build-essential libffi-dev python3-pip gunicorn
Create the user and prepare the folders to be used for the logs:
# adduser ctfd
# mkdir /var/log/CTFd
# chown ctfd. /var/log/CTFd
Clone the GitHub repository and install the necessary Python dependencies:
# su - ctfd
$ git clone https://github.com/CTFd/CTFd.git
$ cd CTFd
$ pip3 install -r requirements.txt
You can now create a systemd unit that will spawn gunicorn and run CTFd (largely based on [2]):
# cat /etc/systemd/system/ctfd.service
Description = CTFd
After = network.target

PermissionsStartOnly = true
PIDFile = /run/CTFd.pid
User = ctfd
Group = ctfd
WorkingDirectory = /home/ctfd/CTFd
ExecStartPre = /bin/mkdir -p /run/CTFd
ExecStartPre = /bin/chown -R ctfd. /run/CTFd
ExecStart = /usr/local/bin/gunicorn 'CTFd:create_app()' --bind '' --workers 2 --worker-class 'gevent' --pid /run/CTFd/CTFd.pid --access-logfile "/var/log/CTFd/access.log" --error-logfile "/var/log/CTFd/error.log"
ExecReload = /bin/kill -s HUP $MAINPID
ExecStop = /bin/kill -s TERM $MAINPID
PrivateTmp = true

WantedBy = multi-user.target
Enable and start the CTFd unit:
# systemctl daemon-reload
# systemctl enable ctfd.service
# systemctl start ctfd.service
You now have a CTFd instance running on port 8000!  I recommend that you put a reverse proxy (e.g. Nginx) in front of it to do static content caching and SSL, put that’s up to you.


[1] https://ctfd.io/ [2] https://bartsimons.me/gunicorn-as-a-systemd-service/

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.