Infra Writeup 2025
Published on 1 September 2025Infra Writeup 2025
DownUnderCTF 2025 was our (“say the line bart”) đ ąď¸iggest event ever! The numbers were big this year, we hit a massive milestone of 5000 signed up players. In fact we had our most ever submissions 75,000+, the most prize money weâve ever given out, we doubled our deployment regions (1 -> 2 math). All with ZERO infra downtime.
As always the infra teamâs goal is to build a secure, stable, and scalable platform for our players from around the world to hack away at. And in the lead up to the event the DUCTF Infra team undertook our biggest challenge yet. For 2025 we finally listened to our past selves and our past writeups and
We wrote documentation
Jk we really need to do that tho.
We wrote another CTF platform!
Introducing noCTF!
We built noCTF to be a scalable and performant CTF platform that works for large scale CTFs like DownUnderCTF ( you may have heard of it, link in bio).
*sales hat on
noCTF is a lightningâĄfast CTF platform built for the modern CTF that scales to 10s of thousands of playersđ¤ź. Donât hold your breathđŤat the launch of your competition and hope that your platform holds up, noCTF has got you coveredđĄď¸.
Packed full of features that CTF admins and players will loveâ¤ď¸and is 100% extensible to customise to fit your CTF vibe. Batteries includedđ.
*sales hat off
Whilst we did build it for the purpose of running DownUnderCTF we didnât want to
make it DownUnderCTF specific and we always developed it with the goal of open
sourcing it. Which we have now done and you can check out all the code here:
https://github.com/noctf-project/noCTF
If you want to just take a look and play around with the UX of the platform. Check out our archive of DownUnderCTF 2025.
FAQ: Why did you create noCTF instead of using some other solution?
- Consistent scaling issues with CTFd over the years
- CTFd theming is not a great experience and is a bit janky
- Other solutions were ok but required a lot of effort to migrate our tooling over
- We can add features that we want!
- Idk, fun and experience, let us be ok
- Internet points â
- This wasnât actually frequently asked
So aside from the general features you can expect from a CTF platform (count flagsđŠand number go up) we implemented a few extra things which really make noCTF standout.
Cool features that are cool:
- Fast fast fast, like f1 car zoomđ
- Audit logs (DFIR noCTF chal 2026?)
- User roles and policies
- Super snappy svelte5 frontend served separately from the API
- Separated backend API and asynchronous workers that can scale independently
- OIDC provider to integrate with other apps
- Scoreboard Divisions and team tags
- Notifications system that integrates with webhooks like Discord bots (E-DOG go woof).
- Event archiving
- Solve/First blood streams
- Tickets support system (coming soonâ˘)
Does this have feature parity with CTFd? Almost. All the main functionality is there, but there are a couple of missing admin operations or features that may not be built or are in progress.
Given we literally had TODO
for the entire admin panel UI about a month before
DownUnderCTF 2025 weâd say that we have made pretty decent progress.
noCTF Technical Overview
How did we make noCTF fast? We separated the platform into 3 distinct components and also did a lot of performance engineering to make sure stuff was fast and snappy.
The Backend API
This is where user requests are handled. Any interaction from the user is handled here. This is what you want to scale when the player base starts to grow. Since all the important data was cached both locally in server memory as well as Redis, stuff was plenty fast. Locally means no network roundtrip even to redis :). Initial benchmarks on our local machines indicated that the server is able to handle 1000+ List Challenges requests on a single core!
Also no ORM! We went with Kysely which is a very nice query builder as it typechecks everything and makes sure that we make no mistakes. Also fully flexible SQL means we can write fairly complex queries to push all the complicated stuff to the DB (and using DB specific features) instead of filtering in-app. This does mean we will only support postgres: no sqlite or mysql sorry (not sorry).
Event-Based Worker
In order to keep things fast and snappy for end users, it makes sense to move long running or complex processes off the backend and into a worker instead. For example, on a correct solve being submitted, the backend will fire off an event through NATS (our trusty message broker) and schedule a recalculation as well as first-blood and solve stream (the last part was just to flex and see if our system can handle it). Other processes that we run include:
- Email queuing and sending
- Support ticket handling (this is still a TODO)
Bespoke Rate-Limiting Setup (which might be too bespoke)
This part was pretty fun to write, and is totally unnecessary to reinvent the wheel đ. Using the trusty Cloudflare blog, we tried to implement both a low-resource and performant rate limiter. First, a local counter was incremented and this was always scheduled to flush to redis by creating a macrotask using setTimeout(). That way, if we receive a large influx of requests from the same user, it would just be committed as a single call to redis, less abuse!
The Frontend
A svelte5 app that can interact with the web API. But this can be swapped out easily for any other frontend of your choice. The backend has all the good swagger documentation. We also didnât build a CMS like CTFd as we simply didnât need it, it adds a lot more complexity than we want. Also organisers can just edit the source directly to add new pages and we felt that itâs a lot nicer since it gives more control.
We also outsourced all the heavy lifting to Cloudflare Pages which allows for the website to be deployed via CDN, keeping everything just a little faster and also putting a little less load on our servers.
Also fully static exports which means we can now keep an archive of the official scoreboard online forever. Check it out here!
noCTF overview of architecture
Penetration Test
Now given we were building a brand new platform that was going to be the target of hackers around the world, we tried our best to develop noCTF with security in mind. However, just like all developers, we make mistakes, and those mistakes turn into security issues which could compromise the integrity of the platform and the CTF as a whole.
To save us from our sins, our amazing Platinum Sponsor Tanto Security offered to perform a pen test of noCTF before we launched and found some super interesting bugs. They have put together a report of the test which shows all their findings and have made it public!
A massive thank you to justinsteven and everyone at Tanto Security for performing this test and helping secure our platform.
We â¤ď¸ TantoSec.
No tools?
Ok letâs upload our challenges to noCTF. Oh wait there isnât a CLI for noCTF.
Ok letâs turn on our first bloodđЏbot. Oh wait that doesnât work with noCTF.
Ok letâs turn on our certificate generator. Oh wait that doesnât work with
noCTF.
noCTF == noTOOLs
There were quite a few issues we ran into, because we built a whole new platform from scratch. Our existing tools and infra scripts just became obsolete. Rip.
So in the lead up (1 week before lol) we had to rip through some features and rebuild some of our tooling so we could keep feature parity with the previous years of DownUnderCTF.
The list keeps going btw
Ok letâs turn on our challenge instancer plugin for noCTF. Oh wait that doesnât
exist.
Ok letâs generate our stats page for 2025 for noCTF. Oh wait that isnât compatible
You get the idea đ
So should you use noCTF for your next CTF? YES, actually maybe.
This is V1 of noCTF that we rushed to get done in time for DownUnderCTF and it seemed
like it worked pretty well! That being said you are likely to run into some bugs
and issues getting started with it as this is the initial launch, but we are keen
to get your bug reports and fix em all up.
Also our donkumentation is pretty bare bones at the moment but we are working on it đ
Ok letâs talk about everything else infra that happened this year.
Writing a challenge proxy 48 hours before the CTF
This really came in quite clutch. As we were trying to deploy web challenges, we found that a lot of our authorâs challenges required direct HTTP protocol manipulation (check out the sodium challenge đ§) which our current reverse proxy Traefik decided to strip or normalise which was not what we wanted. The TCP mode was also flaky as when a user attempts to connect using their browser, it defaults to negotiating HTTP2 using ALPN. This means that they will see the same normalisation issue from the browser. Using openssl s_client does fix the issue as it doesnât negotiate ALPN but leads to an inconsistent player experience.
Luckily we did have some code from a previous experiment last year that we did on streamlining our infra which came to save our bacon. It was a long 6 hour all-nighter 2 days before the CTF to retrofit the test proxy to handle TLS and dynamic port + secret allocation but at 4am on the Wednesday before it was done.
If any of you were snooping around for unreleased challenges by nmapping our infra (tsk tsk), you may have seen this:
== info: This challenge is notcurrently available ==
== proof of work: https://duc.tf/pow-solver ==
Solving the PoW will not give you access to the challenge as itâs configured to only let through a bypass generated by HMACing the challenge with a secret only known to the author. Also one of the organisers loves HMAC a little too much. The proxy does actually support PoW in production as it fronts every k8s challenge service however none of our challenges this year really needed it.
This means that authors can test freely in prod as they only need to authenticate with a secret, instead of us having to configure a firewall with their IP. And we no longer have to manually drop the firewall 5 minutes before the 2nd challenge release, once the time ticks over the PoW is automatically removed. Fully automated challenge release LETS GO!!!
At this point, the codebase for that part is a bit messy (no tests, test in prod) so we wonât release the proxy as of now however we will aim to clean it up some time.
Multi-Regional Deployments
Historically, latency has caused pain for our international audience given that our servers are downunder. A round trip could take something like 200ms for one request which is kinda slow when you are slamming an oracle to get some data.
Multi-regional deployments has been on our wishlist for a while and this year. we finally did it! And it was uhhh⌠kinda straight forward.
We decided to do this about 3 days before the event lol (maybe you see a theme here). Given our existing setups are super modular we could just spin up another cluster to host our challenges in the us-west region, generate the US based config and then deploy all our challenges, create a new DNS entry, and it just worked!
Challenge Infra Automation
A real pain each year is converting our authors’ challenges to deployable challenge units with the right configuration to put on our kubernetes cluster.
We built some tooling around this and internally called it DUCLI. When given a challenge.yaml file and a docker-compose.yaml file, DUCLI can generate a customized kustomize.yml file for each environment depending on if it is a web or TCP challenge and even supports multi-container setups. It also supports isolated challenges using kube-ctf deployments based on provided templates.
This was super handy, but we did run into a lot of edge cases that required manual tweaking (weird env setups, networking weirdness). We hope to keep improving this to make infra config generation much easier. Ideally we go âpls give us a donker compose file and we can make it just work as long as the challenge.yaml is configured correctly ( at least for 90% of cases). This also works super well when you need to apply a config change across 60 challenges and can regenerate config swiftly.
But this led us to the next problem. We had separate configurations for each env we were pushing to (preprod, prod, prod-us). But did not have a way to keep them in sync, or some kind of promotion pipeline to make sure changes from preprod go to prod and that prod and prod-us are in sync. This actually led to one of our challenges having a brief issue because the prod config (but not preprod) was not updated with the latest config.
Some Cool Metrics and Stats
noCTF Request Rate
noCTF Latency
2026 Wishlist
2025 was a pretty massive year in progress for the infra team and we are really proud of the work for this year. As always there is heaps of room to grow so where do we go from here?
- Challenge canaries
- Make noCTF even better and more feature packed
- Ticket integration from within noCTF
- Challenge health dashboards
- Probs better time management so we aren’t doing everything in the last week (can we feature request this?)
Wrapup
We hope you all enjoyed playing DownUnderCTF 2025 and are looking forward to bringing you the 7th edition in 2026. We hope you check out and enjoy noCTF and please let us know what your thoughts on the platform are!
Thanks to our 2025 infra team.
See you in 2026
infra building infra 24 hours before the CTF