Home About Posts Tags Projects Links
CTFd Registration Code Patch
19 September 2019

CTFd is a great platform for building CTF competitions that I wrote about in my last blog post. However, it currently lacks one very basic feature: a registration code.

When hosting a CTF online you may not want it open to the entire internet. Requiring a registration code that you can share with intended participants is a straightforward solution to this. To do this I added a registration code field to the Registration page, as shown here:

Reg Code

I had to edit 3 files inside the CTFd container to make this happen:


CTFd is based on Flask, which is a "a micro web framework written in Python".

For what it's worth seeing, this is a Flask 'hello, world' app:

from flask import Flask
app = Flask(__name__)

def hello():
    return "Hello World!"

if __name__ == "__main__":

This would generate a simple webpage that displays "Hello World!". Before this project I knew nothing about Flask, but I watched a few minutes of a tutorial on Youtube to get the gist of it and then dug in. I wanted to quickly get the reg code working, not become an expert. That's often my goal - learn enough to get done what I need done.

Before diving into more Flask I'm going to cut to the HTML templates because that was the first thing I found digging around inside of the CTFd container and I think makes sense to cover first.

HTML templates and Jinja

CTFd is containerized. Inside the container I dug around looking for anything that looked related to a registration page. If you download the code from their git repository you can find the template here:


A the top of this page you'll see some code like this:

{% block stylesheets %}
{% endblock %}

This is Jinja. Basically, it amounts to placeholders that the Flask scripts replace with other stuff in the HTML templates. The code block above is where CSS properties are inserted into the register.html page. I use a similar method for generating HTML for this blog from templates.

The first and easiest thing I did was remove the OAuth button from the page because I didn't want to use it. That was simple enough, just blow this line away:

<a class="btn btn-secondary btn-lg btn-block" href="{{ url_for('auth.oauth_login') }}">Login with Major League Cyber</a>

Then if you look a little further you'll find the registration form and the input text boxes. The snippet for the username is show here:

<form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal">
<div class="form-group">
    <label for="name-input">
        User Name
    <input class="form-control" type="text" name="name" id="name-input" {% if name %}value="{{ name }}"{% endif %} />

All I did was duplicate this and make another text input for the registration code. Replace "name" with "regcode" and that's really all it takes for the HTML template. In the case of the username input above the Flask script looks for those "{% %}" and "{{ }}" blocks and interacts with them when you click the Submit button. This is a simplified explanation but it's all you need to make this work.

Back to the Flask

The auth.py script mentioned above is what does the actual work. I looked through the script and found where it processed the username, password, and email inputs, and duplicated those for the "regcode" input I created on the HTML template.

There is code to get the regcode string from the HTML form when the "Submit" button is pressed, then compare it the registration code, which is "qwerty" in the example below. This will then trigger an error if the submitted code does not match. I feel like it is mostly self explanatory if you read through the code. But it works like a charm and was a success.

if request.method == "POST":
    name = request.form["name"]
    email_address = request.form["email"]
    password = request.form["password"]
    regcode = request.form["regcode"]  # get html form input

    reg_valid = regcode == "qwerty"  # this is the regcode!!

    name_len = len(name) == 0
    names = Users.query.add_columns("name", "id").filter_by(name=name).first()
    emails = (
    Users.query.add_columns("email", "id")
    pass_short = len(password) == 0
    pass_long = len(password) > 128
    valid_email = validators.validate_email(request.form["email"])
    team_name_email_check = validators.validate_email(name)

    if not valid_email:
    errors.append("Please enter a valid email address")
    if email.check_email_is_whitelisted(email_address) is False:
        "Only email addresses under {domains} may register".format(
    if names:
    errors.append("That user name is already taken")
    if team_name_email_check is True:
    errors.append("Your user name cannot be an email address")
    if emails:
    errors.append("That email has already been used")
    if pass_short:
    errors.append("Pick a longer password")
    if pass_long:
    errors.append("Pick a shorter password")
    if name_len:
    errors.append("Pick a longer user name")
    if not reg_valid:  # check reg code!!
    errors.append("Your registration code is invalid")

    if len(errors) > 0:
    return render_template(
        regcode=request.form["regcode"]  # keeps code in re-rendered form

After making those edits I replaced the three files in the Docker container with my own, restarted the CTFd container and was good to go! I also got rid of the OAuth button on the login page, so that updated file is in the git repository as well. It was surprisingly easy to modify the site and make it do what I wanted. Very cool.

Thoughts on Flask

I really like what I saw with Flask and may actually try and rebuild this blog using it at some point. It sounds like not only a fun project, but a way to make homegrown blog life a lot more simple... or maybe less simple?

You can find my code here: https://github.com/chadpierce/CTFd-registration-patch