Our first time arranging a CTF competition

Jari Jääskelä
OUSPG
Published in
6 min readNov 27, 2020
One of the challenges. The origin of the image is unknown, it circulates on Reddit from time to time.
One of the challenges. The origin of the image is unknown, it circulates on Reddit from time to time.

This year we held an entry-level CTF (capture the flag) event as a part of the workshops at CriM 2020. CriM is an annual event with workshops and lectures that focuses on security and privacy of digital systems. In CTF, players compete against each other by solving different kinds of challenges, usually related to computer security. This was our first time arranging a CTF competition.

The CTF

We chose CTFd as our CTF platform because it seems to be the most mature platform. The CTF was running for two days, and it had ~30 participants. At this scale, we did not have any problems running just a single CTFd instance without caching.

Challenge topics were mostly related to digital forensics. Initially, the idea was to create workshop material with a lot of variety, which could be solved using the security analysis tools containerized by the CinCan project. In the end, this expanded into CTF competition. After the CTF was over, in the actual workshop, we gave some hints for solving the challenges and showcased how some of these challenges could be solved.

Forensics theme had Android disk forensics, Windows memory forensics, steganography and some other related tasks. We also had some web and network related ones, including container escape. However, they were not only limited to these; the most discussion (and the amount of attempts) seemed to be from a topic which did not even require a computer at all.

See keypad from the landing image (lock.jpg).

Most of the participants were students, and difficulty seemed to be at a reasonable level. Hopefully someone learnt something!

Behind the scenes

In this section, we will take a look into our infrastructure and how we managed the CTFd platform.

Infrastructure

In Finland, IT Center for Science (CSC) provides free cloud computing resources for education and research purposes for universities. We decided to use OKD managed by CSC, which is a Kubernetes distribution by Red Hat. This was chosen mainly because we had previous experience with Kubernetes and containers.

If you are not familiar with Kubernetes, reading the official docs or the illustrated children’s guide to Kubernetes is a good way to get started :). In short, Kubernetes is a platform for managing containerized workloads. Kubernetes resources are configured declaratively using configuration files that can (should) be stored in a version control system, such as git.

CTFd has an official container image that we used to deploy CTFd on Kubernetes using the following manifest:

I won’t describe this file in detail here, as a summary, if the container crashes or freezes (liveness probe timeouts), the pod will be rescheduled. The configuration file and CTFd environment variables are stored as a Kubernetes resource (Secret). Challenge files are stored in the persistent volume (uploads). The service is responsible for routing traffic to the pods.

CTFd environment variables are stored in the “ctfd-secrets” resource. Secret can be created from a environment file using kubectl:

kubectl create secret generic ctfd-secrets --from-env-file=./ctfd-secrets.ini

We decided to use MariaDB for storing CTFd settings, which was deployed using the OKD web console and we used “filesystem” filestore to store challenge files, so our environment file looks as follows:

SECRET_KEY=SECRET_HERE                       DATABASE_URL=mysql+pymysql://USER:PASS@HOST/DB                                         
UPLOAD_FOLDER=/var/uploads UPLOAD_PROVIDER=filesystem

Because we used a persistent volume to store the challenges, when the CTFd instance is scheduled on another node, the previously uploaded challenge files remain accessible. Because the volume has “ReadDiskMany” access mode, it can be mounted into multiple containers at the same time. Alternatively, CTFd could be configured to use a different storage provider, such as S3.

OKD has a “Route”, which we used to expose the CTFd service as follows:

The exposed service is served using HTTPS (terminated at the edge). The traffic between the router and the CTFd is plain HTTP. In this case, CSC had preconfigured the DNS and the certificate for their domain (*.rahtiapp.fi) that we used, so we did not have to touch those.

The frontend

On the frontend side, CTFd has theming support. By default, the appearance of the platform might be a bit dull. There are few options to modify UX for the end-user — build theme from scratch or just “hack the CSS” with the override options. The way how HTML elements are named is consistent between different pages and gives opportunity for quite big changes with quite little modifications — doable straight from the admin panel.

CTFd also allows adding new sites (with HTML or Markdown) using the admin panel, which is useful when we have short-lived website with multiple moderators. In the long term, this might bring the chaos.

Challenge management

CTFd has a feature that allows storing challenges as yaml files. This is a great feature because these files can be stored in the version control system and the changes can be synced using the “ctf challenge sync challenge.yaml” ctfd CLI command. Actual challenge files or other information is distributed automatically into a configured CTFd instance if the yaml is correctly defined. This could even be integrated into a CI/CD pipeline.

Most of our challenges were offline, and those that were online were deployed in different environments. When challenges are deployed in the same cluster, ensuring that the challenges are isolated property from other CTF infrastructure is a good idea. See “Capturing all the flags in BSidesSF CTF by pwning our infrastructure” for how the infrastructure might be hacked if not configured securely.

Even though the CTF rules might forbid hacking the infrastructure, still some caution should be applied. After all, the audience is here to hack things.

Conclusions

Overall, our experience arranging CTF competition for the first time was good. CTFd as a platform was suitable for our use case. However, there were few things we could have improved. The difficulty of the tasks was hard to estimate. For some of the challenges, the amount of acquired points was not in line with how difficult the challenge was. Potential solution to this is to use dynamic scoring instead, which adjusts the challenge score automatically based on how many solve it.

We had one chain of three challenges. All of these were in the same Android disk image. These had to be solved in the right order, otherwise the player would miss hints and the flag of a later stage could not be submitted into the platform. In retrospect, was a poor choice because some players skipped the first flag, in which case they could not submit the second flag even if they found it. Giving more guidance in the challenge description would have been a good idea too.

The infrastructure described here should work fine with higher player counts thanks to Kubernetes load balancing and scaling (vertical and horizontal). Though, adding Redis cache may be necessary, as this was recommended by the CTFd docs. Also, offloading challenge file storage to S3 may be preferable over using local file storage.

On top of this, properties like first-blood with minor point increases for the first solver (and badges of course!) could make it more interesting! The full CriM 2020 program is available here.

Writers: Jari Jääskelä, Jukka Pajukangas & Niklas Saari

CTF organizers: Lauri Haverinen, Jari Jääskelä, Rauli Kaksonen, Juha Kälkäinen, Jukka Pajukangas & Niklas Saari

--

--