Building a CTF
On December 3, 2021 I hosted a CTF (Capture the Flag) for my workplace (Sourcetoad). For those unaware, you aren't running outside to find a real life flag in this iteration. This is a contest where competitors have a list of challenges to individually compete in - these all revolve around solving some challenge to obtain a "flag" (In this case, a string of characters).
Finding a platform
So right out of the gate you'll need to find a platform to run this contest. If you are enormous company - chances are you might already have an internal tool or open sourced iteration of a platform.
For me, I found two services that appeared to offer custom built CTFs, which was CTFd & Hack The Box. The pricing page for CTFd had plans from $50, $100 and $300 and I could not find any pricing on Hack The Box without asking for a quote.
So at that point, I decided to go forward with the Basic plan ($50/month) of CTFd. I'd honestly only ever competed in 1 CTF prior and I was already building my own, so figured I would start small.
Building a theme
Now that I had a platform, I wanted to make sure I kept a theme among the CTF. Much like you take the effort during a presentation deck or matching decor at a wedding - I think a unified theme during a CTF helps draw it all together.
Knowing that a bad website intentional design took place at work once, this seemed like a little effort pattern to continue. So I just looked for intentional bad CSS frameworks and stumbled upon Geo for Bootstrap, a 2.3 iteration of Bootstrap mocking Geocities of the past.
While this theme was a perfect vision of a bad site, I wanted to tone it down a bit so it was easier to read text/information. However, prior to doing that I wanted to extract the framework into one small compiled file with inline images so I could attach it to each challenge easily.
Just in case a competitor got obsessed with this compiled file, I left a note in the credit of the file.
* Bootstrap v2.3.1
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* Designed and built with all the love in the world @twitter by @mdo and @fat.
* There is nothing in this file that is part of the CTF. It was simply added to speed the design of the template.
So no competitors would get lost in trying to dig through this file for some hidden secret. Looking back, a few folks did bring up these comments for any uninvolved files and appreciated the hint to save time digging into them.
So now I had a theme easily attachable to questions which built a page that looked like roughly (Question 1) like:
Building the challenges
Now this part is not going to be easy, the more help you recruit to build these - the less folks that can honestly take part in the CTF (since they helped built it). Now this doesn't really apply again at huge companies, because a large company might just designate Team A to build the CTF and all other teams to take part.
My company was 50 people though and probably only ~35 engineers, so I wanted to build all the challenges so every other person could take part without knowing anything ahead of time.
Challenges normally fit under categories and that is highly specific to each organization and company. We specialize in web & mobile software so I took focus to all types of web challenges:
- OWASP Top Ten
- Basic Cryptography
- Basic Stenography
- Laravel Vulnerabilities
- PHP Vulnerabilities
- Scripting Exercises
The OWASP Top Ten were going to be the main focus point, but designing intentionally insecure challenges that leveraged all of those got a bit difficult. This is where designing a simple question to hide text in an image exif data could pad an additional question.
Finding an older CVE for either Laravel or PHP helped for many challenges. For example:
- Expecty Bindy (Question 30) - CVE-2021-21263
- Poison (Question 32) - CVE-2006-7243
- Mass Effect (Question 27) - CVE-2020-24940
- Ignite (Question 25) - CVE-2021-3129
These were a great dive to really fully understand vulnerabilities reported. For example, lets take
CVE-2021-21263. This was not too long ago so I attempted to exploit this for a challenge and it refused to work. The note as shown above just shows the Laravel versions affected, but I continued to dig into this until it made sense.
Turns out this was being tracked in PHP bug 72368, which was a bug to explain that if the amount of variables does not equal the amount of tokens passed then an exception should have been raised.
PHP member (mbeccati) left a comment and said:
pdo_pgsqlraise an exception, as expected. I haven't tested the other drivers, but I will attach a patch that adds a PDO Common test for the issue.
So this explains why my test CTF challenge using SQLite did not work as well as PostgreSQL. This also explains why using using MySQL on PHP8 was also not vulnerable, instead giving me:
SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens
So I learned a lot and I honestly wish these details were included in the original CVE, but were not. This issue was really only for MySQL drivers at PHP74 or lower and that was not listed.
Moving back to the general set of challenges, one thing that is continually difficult is identifying a difficulty for a challenge. This takes a good amount of thinking how your competitors will attempt these challenges - what helped here for me is finishing the CTF weeks prior to the date so I could have two friends run it through to check the difficulties described.
Additionally, knowing my work uses a form of the modified Fibonacci sequence for estimating work - I tried to stay true to those estimates when creating challenges.
Scoring the challenges
This is one part I would easily change if hosting another CTF. You can have so many different types of scoring and I was attracted to the Dynamic challenge point scaling that CTFd offered. It sounded clever because the question points decrease as more people solve it. Though in theory, end users don't like it and losing 1 or 2 points even as that decay affects others is not appreciated well.
This is what my challenge breakdown looked like after everyone went through. Since I used a 30 solve max until 5-20% of the question points were gone - most questions did not decay at all. However, some questions that were originally 100 points had fallen all the way to 80. This is just all about finding the best configuration for your setup.
If I was building a CTF for the entire world to take part, then yes this point system sounds perfect for it. However, an internal company event was not the right call for this.
Hosting the event
If you are using a service - you have to trust the service will work and focus your effort elsewhere. I used this time to have the scoreboard live and refreshing every 5 minutes as well as just listening to the chatter of folks solving challenges.
You might have to intervene - like one of my questions that intended folks to use a specific online steganography site had somehow a similar, but different image uploaded to it that conflicted with mine. So all folks that attempted the challenge were getting an error when they shouldn't. So I remade challenge 6 on the fly during the event.
You might have to adjust a hint live or toss out some vague tips n tricks to keep folks engaged. As the event nears the end, keep folks up to date on first solves of complex challenges and make sure to present some sort of analytics at the end.
I made sure to draw focus to many stats
- Most solved challenge(s)
- Least solved challenge(s)
- Most 1st solves
- Top 10 scores
This helps give props to the competitors that placed in various fields and helps see which challenges were easy or hard.
At this point - your event is over. You can either keep it running for a few weeks to let folks take part outside of a work sanctioned event or just end it. Whatever you decide - I believe its crucial to provide solutions for all challenges to let the confusion that plagued competitors be resolved.
These can be simple markdown docs collected in a folder. After all of that is done - you've successfully created, organized, built and solved a CTF.