Lessons Learned Running My First Corporate CTF
Or: what a month of hosting a company-wide Capture the Flag taught me about people, infrastructure, and office politics.
A while back I volunteered to run a Capture the Flag for Security Awareness Month at the global company where I work. It felt like a natural fit. I used to be a pentester, I’ve been playing CTFs for years, and I had strong opinions about how boring most corporate security awareness programs are. How hard could it be?
As it turns out, the hacking was the easy part.
The event ran for 30 days in October 2024, across eight countries. 117 people registered and 107 of them actually played, working through 61 challenges in five categories worth 3,775 points. Total infrastructure bill for the month: about $500.
It worked. People learned things, teams formed across four countries between colleagues who had never spoken, and the security team stopped being the group that says no and became the group that runs the fun thing in October. It also produced exactly one piece of internal drama that nearly killed the program in its first year.
Here is what I would tell anyone about to do this for the first time.

1. People cheat. Plan for it before you launch, not after
I want to lead with this one because it surprised me the most.
These are your colleagues. They are not anonymous internet strangers. They still cheat. Not everyone, not even many, but enough that you will spend real time on it. Flags get pasted into group chats. Someone screenshots a quiz answer key. A team quietly recruits a sixth member. Someone tries to brute force the scoreboard instead of the challenge.
The mistake I almost made was treating rules as boilerplate. They are not boilerplate, they are your only leverage. Before a single container goes up, write and publish:
- Rules of play, including team size limits, what counts as collaboration, and what counts as sharing.
- A code of conduct, covering harassment, sportsmanship, and behavior toward other participants.
- Terms of service, covering eligibility, event window, scoring, prizes, and your right to disqualify.
- A privacy notice, covering what participant data you collect (name, team, scores, IP addresses, timing) and how long you keep it.
That last one matters more than people expect. You are running a platform that logs IPs and solve times for employees. Write down why, and get it reviewed by whoever owns privacy at your company. If you are asking the whole company to take security seriously, you do not get to be sloppy about their data.
Then, and this is the part that requires spine: enforce it. Publish the consequence (disqualification of the individual and their team) and actually apply it. The first time you let something slide because the person is senior, or well liked, or it is only one flag, your rules become decoration and every honest participant learns that playing straight is for suckers.
Practical detections that cost nothing:
- Look at solve timestamps. Five accounts solving the same hard challenge within ninety seconds of each other is not a coincidence.
- Look at the order of solves. A legitimate player solves easy things before hard things. A flag receiver does not.
- Watch for correlated IPs and impossible travel.
- Instrument the challenges themselves. Per-user flags are the real fix if you have time to build them, because then a leaked flag identifies its own source.
Per user flags are a good to have, it’s most likely impossible to implement without real time investment
2. Centralize the flags and guard them like credentials
Flags sprawl. That is their default behavior, and you have to fight it.
By the time the first challenges were ready, the same flag value existed in the challenge source code, in the answer key of a quiz form, in the private walkthrough written for admins, in the ticket where somebody proposed the challenge, and in a chat thread where two contributors were debugging it. Five copies, five different access control models, and only one of them was a model I had actually thought about.
Every copy is a leak path, and it only takes one.
So we moved to a single register: one spreadsheet, one owner, access restricted to the organizers and nobody else. That sheet held the canonical value for every flag in the event. Platform configuration was populated from it. Walkthroughs referenced it rather than embedding values. Challenge repositories were archived once the challenge was approved and moved.
The rules that made it work:
- No flag ever gets pasted into chat. Not to debug, not to confirm, not just this once.
- No flag in a ticket, a wiki page, or a calendar invite.
- Don’t hardcode the flags in your code, add them as environmental variables. You don’t want participants finding the flags in the code somewhere in github.
- Rotate every flag immediately before launch, so anything that leaked during authoring is already dead on arrival.
- Label which flags are secret and which are public by design. We hid flags in the slides of every talk that month, so those were meant to be found by anyone in the audience. Mixing them into the same unlabeled list as your hard challenge flags is how somebody helpfully shares the wrong one.
- The quiz flag and the platform flag for the same challenge have to match exactly. A register is what makes that reliable, rather than something you verify by hand at 2am.
This is the operational half of lesson one. Detecting flag sharing is useless if the answer key was quietly readable by half the company the entire time.
3. Access is a design constraint, not an afterthought
This was the single decision that most shaped the architecture, and I nearly got it wrong.
My first instinct was the obvious one: build it in the internal environment, behind the VPN, reachable from the corporate network. It is where all our tooling lives. It is where I have permissions. It is the path of least resistance.
Then I actually looked at who I wanted to play.
Our engineering population is a minority of the company. The customer operations teams, the compliance analysts, the support agents, the people in offices whose daily job never touches a development environment: none of them have VPN access, and none of them need it. Building on the internal network would have quietly excluded most of the company from a company-wide event and turned “security awareness for everyone” into “a puzzle box for the engineers.”
So the requirement inverted. The platform had to be reachable from a normal laptop on a normal network, from any office, in any country, without special access.
Which of course means it is on the internet, which means the next two lessons.
If you take one thing from this post: enumerate your intended audience and their real access level before you design anything. The population you want to reach determines your topology. Not the other way around.
4. Do not run deliberately vulnerable code anywhere near production
Half your challenges are, by definition, applications with exploitable bugs in them. SQL injection targets. Broken object level authorization. Unsafe deserialization. You are inviting a hundred people, some of whom are very good, to attack them, using whatever they have lying around.
That belongs nowhere near your corporate network, your production VPC, or anything that has ever seen customer data.
What I did:
- A dedicated cloud sandbox account with no peering, no shared credentials, and no route back to anything that matters.
- Challenge containers isolated from each other and blocked from initiating outbound connections, so a container is a dead end rather than a launch point.
- Challenge instances rebuilt on a 24 hour cycle, both to reset state after someone inevitably destroys the app and to keep the blast radius of any persistence short. I put that fact directly in the challenge description so players did not file bugs about their shells disappearing.
- Zero real data anywhere. Every dataset in every challenge was synthetic.
One more thing worth saying out loud: get this reviewed. I am the AppSec person and I still had a colleague poke at the design. If your CTF becomes the initial access vector in your own incident, no one is going to remember the part where everybody had fun.
5. Ruthlessly reduce the work that lands on you
I cannot stress this enough. You are one person, probably doing this on top of your actual job. Every degree of freedom you grant someone else becomes a support ticket for you.
The clearest example from our event: the non technical challenges were quizzes based on presentations, contributed by people from across the security and privacy team. I published one form template and made it mandatory. Same structure, same scoring behavior, same flag mechanism, same result email, same linked response sheet, same authentication requirement, minimum five questions, maximum fifteen, one hundred percent correct to earn the flag.
Anything not built on that template was not supported. Full stop.
This felt rigid and slightly rude when I wrote it. It was the highest leverage rule of the entire event. A custom form is a custom failure mode, and every custom failure mode arrives as a direct message to you at 11pm from an office on the other side of the world asking why the flag did not appear.
The same principle applied to technical challenges: a standard structure (title, subtitle, description, sample flag format, an educational section explaining what the vulnerability is, why it is dangerous, and how to prevent it, plus references), a required private walkthrough for admins so I could validate and unblock, a common visual style, and a single repository where approved challenges landed. Contributors grumbled for about ten minutes and then appreciated having a template instead of a blank page.
Standardize the interface. Refuse the exceptions. Your future self is the one paying for the exceptions.
6. Make it fun, and make the fun visible
A CTF with no personality is just mandatory training with extra steps.
We built a theme. There was a mascot, a color palette, a custom platform theme, a landing page that greeted you in character, and an in-joke that ran through the whole event. It cost very little and it changed the tone completely. We provided tshirts and stickets with the mascots. People screenshotted the mascot. People used the theme colors in their team names. The final slide of the awards ceremony had a flag hidden in it, because of course it did.
The award ceremony itself was worth the effort: a live session with the stats, shoutouts to the highest participation offices, team results, individual podium, and personalized digital certificates for the winners signed by the organizers. Fifteen minutes of genuine celebration for people who are almost never celebrated in a security context.
Theming is not decoration. It is the difference between an event people remember and a link they ignored in a channel.
7. Engineer collaboration into the scoring
We are spread across offices in multiple regions and time zones, and I wanted this to pull people together rather than let the usual clusters compete in isolation.
So collaboration was not encouraged, it was made mechanically advantageous:
- Players registered individually but competed in teams of three to five.
- Certain challenges were local only, physically available in one office and nowhere else. If your team had nobody in that office, those points were simply unreachable.
- A few of these were deliberately non digital. Lockpicking at one site, on site puzzles at others.
- Team score was the sum of member contributions, so the correct strategy was to split by strength and share findings.
- Diversity of the team was the published tiebreaker.
The result was exactly what I hoped for. Teams formed across three and four countries. People pinged colleagues they had never spoken to because that person happened to sit in the office with the challenge they needed. One team was made of people from four different countries.
If you want cross office collaboration, do not put it in the announcement post as a nice sentiment. Put it in the scoring function.
8. Your challenges are subject to local law, and you have probably not checked
We were planning a lockpicking village. It is one of the most reliably popular things at any security event, it gets non technical people genuinely excited, and I had run one before without a second thought. The plan was to ship tool kits to several offices so more than one location could host it.
Then somebody who actually knew asked whether that was legal in the places we were shipping to.
It was not. In some European jurisdictions, possession of lockpicking tools is restricted to licensed professionals. An ordinary employee found holding a set has a real personal problem, and the company would most likely be looking at a fine on top of it. Something completely routine at a conference in one country is a legal matter in another.
We caught it during planning rather than after the tools landed, and I would call that luck rather than diligence.
The general lesson: the moment your event crosses a border it crosses into other legal regimes, and the things that catch you are the things so normal at home that you never think to ask.
- Anything physical. Tools, devices, hardware. Import restrictions and possession laws vary between countries and are not intuitive.
- Participant data. Your platform logs names, teams, scores, IP addresses, and timing for employees in multiple countries. Different regimes, different retention expectations, different rules on cross border transfer.
- Prizes. Cash and cash equivalents can count as a taxable benefit, which turns your fun prize into a payroll question. Thresholds and treatment differ by country.
- Participation itself. If people are playing outside working hours, some jurisdictions have opinions about that, particularly for hourly staff.
The fix is cheap. For every office you intend to touch, ask somebody local and loop in legal and HR before you ship anything physical or pay anything out. It is one short email and a week of waiting, which is considerably less painful than the alternative.
We are trained to reason carefully about technical risk. Legal risk across a dozen jurisdictions is a different muscle, and most of us have not built it.
9. Prizes drive participation. They also drive cheating
We funded real prizes: gift cards for every member of the top two teams, and a cash equivalent tier for the top three individuals. Everyone on the podium also got a digital certificate.
Participation went up noticeably once the prizes were announced. So did the incentive to cut corners. These are the same lever. You do not get the first effect without accepting the second, and the moment there is money on the line, “it is just for fun” stops being an adequate answer to a rules dispute.
Which is the real argument for lesson one. Prizes are what turn your rules document from a formality into a thing you will actually have to cite.
Budget wise, the prize pool was modest and easily justified against the engagement it bought. If you cannot get budget, digital certificates, a shoutout from a senior leader, and a live ceremony still move the needle more than you would think.
10. You will have detractors. Line up your promoters in advance
This is the lesson I did not see coming, and the one most likely to determine whether you get to run a second event.
The individual podium was swept by people from a non engineering function. They were not developers or security people. They were relentless, they worked as a team, they read documentation, they attacked the non technical challenges systematically, and they earned it.
One participant took that badly. Losing to non technical colleagues was apparently intolerable, and instead of taking it to me, they escalated by email to our CSO, framing the result as evidence that the event was flawed.
That single email had a real chance of ending the program. Executive sponsors do not usually have context. What they have is a complaint in their inbox and a vague sense that the thing generated friction.
What saved it was something I had not planned and got lucky on: the manager of one of our international offices independently wrote to the same executive to say that their team had learned a great deal, had collaborated across borders in a way they normally never do, and had come out of the month stronger and more connected.
Two emails. One negative, one positive. The positive one made the difference.
So do not leave it to luck:
- Run a feedback survey and close it before the drama window opens. Keep the quotes.
- Ask managers whose teams participated heavily to send a note upward. They are usually happy to, they just need to be asked.
- Send your sponsor a short summary with the numbers as soon as the event ends: registration, participation, countries, challenges solved, cost. Get your framing in first.
- When a complaint lands, respond factually and without defensiveness. Rules, scores, and timestamps. No editorializing about the complainant.
Every visible internal program produces detractors. The question is only whether the promoters are audible at the same time.
11. Build for non technical people, seriously and not as a token
Related, and important enough to stand alone.
If every challenge is a web exploit, you have built an event for the security team and invited everyone else to feel inadequate. Our five categories deliberately spanned the range:
- Technical challenges: SQL injection at two difficulty levels, one with an encryption layer on top, insecure direct object reference, unsafe deserialization.
- Non technical challenges: quizzes on policy and process, threat modeling scenarios, security judgment questions, all built on globally available documentation so nobody was disadvantaged by geography.
- Presentation flags: every talk the security team gave during the month contained a hidden flag. This one is close to free and it dramatically improved attendance.
- On site challenges: physical puzzles in each office, including lockpicking.
- Social and participation challenges: including a happy hour with the security team.
Difficulty was scored so that a stack of well executed easy challenges could beat a couple of hard ones. That is a design choice, and it is the choice that let a compliance analyst take first place overall.
That outcome is not a bug in the event. It is the single most valuable thing the event produced. It is also, as covered above, the thing that generated the complaint. Both of those are true at once, and I would design it the same way again.
The infrastructure
Short version: an open source CTF platform, containerized, in a dedicated cloud sandbox account, deployed entirely with Terraform.
The shape of it:
- Load balancer in front, doing authentication. This is the piece I would most recommend copying. The platform was open to the internet, so the load balancer enforced SSO via OIDC before any request reached the application. No corporate SSO session, no access. That gave me a single, auditable front door, employee only access without a VPN, and identity on every request without building auth into the platform.
- Container service running the platform, horizontally scaled, with challenges in isolated containers.
- Managed relational database for platform state, managed Redis for caching and sessions, object storage for uploads and logs.
- A custom domain with no relationship to the company name. Since the thing was internet facing, I did not want a corporate subdomain hosting deliberately vulnerable applications, and I did not want the event to look like corporate infrastructure to anyone scanning. A cheap unrelated domain solved both.
- All of it in Terraform. There are good community modules for CTFd on AWS that get you most of the way, and the rest was glue.
A note on why I built it this way rather than the fastest way. My first sketch was a single instance running Docker behind nginx with certificates from Let’s Encrypt and SSH through a bastion, because that is what I already knew and it would have been quicker. I deliberately went the other direction and built it on managed container infrastructure with Terraform instead, because I moved into AppSec from a background that involved a lot more hands on infrastructure, and skills you do not use get rusty fast. An ephemeral, low stakes, month long project with a hard deadline is a very good excuse to stay sharp on infrastructure as code. If you have a similar gap in your practice, a CTF is a great place to close it.
Cost. The whole month came in around $500. That is the entire event: platform, database, cache, load balancer, storage, and every challenge container, for 30 days and about a hundred players.
Two things kept it there. First, everything was sized for the real load, which for an internal event is small. Second, and more importantly, terraform destroy at the end of the month took it all down cleanly in one shot. Nothing lingered, nothing quietly billed into the next quarter, and there was no half decommissioned sandbox sitting around for someone to find in an audit six months later.
Ephemeral by design is both the cheap answer and the secure answer. Build it so that tearing it down is a single command, and then actually run that command.
What I would do differently
- Per-user flags from day one, so leaks are self attributing.
- A published, staffed support channel with hours, rather than absorbing everything personally at all hours.
- Collect testimonials during the event, not after. People are enthusiastic while they are playing and busy once it ends.
- A written disqualification process agreed with HR and my leadership before launch, so that if I need to remove a team, it is a procedure rather than an argument.
- Stagger challenge releases across the month. A single drop means a burst of activity in week one and silence in week three.
Worth it?
Completely. A hundred people across eight countries voluntarily spent a month learning about SQL injection, access control, threat modeling, and physical security, and had a good enough time doing it that they asked when the next one is.
You will do more project management than hacking. Budget accordingly, write your rules like you will need them, keep your vulnerable code far away from anything real, and make sure the people who loved it are as loud as the one person who did not.
No_m0r3_F14g5