October 23, 2018
CTFd on Ubuntu 18.04
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 gunicornCreate the user and prepare the folders to be used for the logs:
# adduser ctfd # mkdir /var/log/CTFd # chown ctfd. /var/log/CTFdClone 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.txtYou can now create a systemd unit that will spawn gunicorn and run CTFd (largely based on [2]):
# cat /etc/systemd/system/ctfd.service [Unit] Description = CTFd After = network.target [Service] 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 '0.0.0.0:8000' --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 [Install] WantedBy = multi-user.targetEnable and start the CTFd unit:
# systemctl daemon-reload # systemctl enable ctfd.service # systemctl start ctfd.serviceYou 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.