DDC-CTF: Building a 121-Challenge CTF Platform for a National Cybersecurity Conclave

I am the Vice President of DDC — Digital Defence Club at Chaitanya Bharathi Institute of Technology (CBIT). this year in feb , we organised a national-level cybersecurity conclave: a full-day event combining workshops, a hackathon, and a 36-hour CTF running in parallel.

My job had two parts. First: build the entire CTF platform from scratch. Second: be on-site monitoring the live competition for the full 36 hours while 100+ participants competed.

This is the engineering post-mortem. What I built, why I built it the way I did, and what I learned running a CTF at scale under a real deadline with real stakes.


The Event: CyberFest 2026

CyberFest was structured as three back-to-back tracks:

  1. Workshops — Security professionals and students running hands-on sessions (web exploitation, forensics, OSINT, malware analysis)
  2. Hackathon — A parallel 24-hour build challenge
  3. CTF — Our 36-hour Capture The Flag, which I both built and monitored live

The CTF ran on a platform I wrote entirely myself — backend, frontend, admin panel, scoreboard, challenge files, and auth flow. DDC as a club handled logistics and judging. I handled infrastructure.


The Constraints That Shaped Everything

Before writing a single line of code, I listed my hard constraints:

  1. Teams, not individuals. CyberFest runs on team-based participation. Auth had to be by team name—no passwords, no email verification noise.
  2. Admin-gated registration. A shared admin key would let me gate who could register. With a national-level event, you cannot have open registration.
  3. Mix of static and downloadable challenges. Some challenges would be pure text (OSINT). Others needed zip file downloads (forensics artefacts, APKs, binaries). The architecture had to handle both cleanly.
  4. Real-time scoreboard. A 36-hour competition with live teams needs a live scoreboard — not one that updates every 5 minutes.
  5. Zero budget. DDC is a student club. Deployed on Render free tier. MongoDB Atlas free cluster. No paid infrastructure.

Designing the Challenge Taxonomy

I landed on a tiered difficulty system with eight distinct categories. The final event ran 121 challenges — far more than the initial 51 I planned. As the event date approached, I kept writing more. Every new category got its own seed script.

Tier / Category Count Points Notes
Easy 16 100 pts Forensics, Reversing, Crypto, Misc, Stego
Medium 35 200 pts Crypto, Forensics, Stego, Reversing, Misc
Hard 10 300 pts Reversing
Insane 20 500 pts Crypto
OSINT 20 150 pts Text-only, no files
Web 11 200–400 pts Web exploitation
APK 8 250–400 pts Android APK reversing
AI 5 300 pts LLM prompt injection / model analysis
Master Web 11 500→50 pts Dynamic scoring, decay-based
Total ~121 Deployed on Render + Vercel
pie title Challenge Distribution — CyberFest 2026 CTF "Easy — 100pts" : 16 "Medium — 200pts" : 35 "Hard — 300pts" : 10 "Insane — 500pts" : 20 "OSINT — 150pts" : 20 "Web — 200–400pts" : 11 "APK — 250–400pts" : 8 "AI — 300pts" : 5 "Master Web — 500→50pts" : 11

The Master Web category was the most ambitious. Instead of static points, I implemented dynamic scoring: a challenge starts at 500 points and decays with each additional solve (500 → 450 → … → 50 minimum). The live site for those challenges was deployed separately at cyberfest2026-roan.vercel.app — participants had to find flags hidden inside a real React app’s source, console, service workers, WebAssembly comments, and JSON-LD metadata.

The APK challenges required the most tooling — I built generate_apk_challenges.py specifically to embed flags inside compiled Android bytecode and resource files at varying obfuscation levels.

The OSINT challenges were my favourite to design. No file downloads. Just a question, your brain, and the open internet. Twenty questions across geospatial intelligence, social media forensics, corporate recon, and historical data archeology.


The Backend: Node.js + Express + MongoDB

I chose Node.js with Express for speed of development and Mongoose for MongoDB ODM. The schema design was the most critical part.

The Team Model

// models/Team.js
const TeamSchema = new mongoose.Schema({
  name:      { type: String, required: true, unique: true, trim: true },
  token:     { type: String }, // JWT stored for session continuity
  solves:    [{ type: mongoose.Schema.Types.ObjectId, ref: 'Submission' }],
  score:     { type: Number, default: 0 },
  createdAt: { type: Date, default: Date.now }
});

No passwords. A team name is your identity. Admin key gates creation.

The Submission Model

// models/Submission.js
const SubmissionSchema = new mongoose.Schema({
  team:        { type: mongoose.Schema.Types.ObjectId, ref: 'Team', required: true },
  challenge:   { type: mongoose.Schema.Types.ObjectId, ref: 'Challenge', required: true },
  submittedAt: { type: Date, default: Date.now },
  correct:     { type: Boolean, required: true }
});

Tracking both correct and incorrect submissions gives you rich audit logs for post-event analysis.

Auth Flow

Registration requires a team name and the admin secret. On success, a JWT (24h expiry) is issued. On subsequent visits, teams log in with just their team name—the server verifies the JWT from localStorage. This keeps the UX dead simple while maintaining server-side authority.

// routes/auth.js (simplified)
router.post('/register', async (req, res) => {
  const { teamName, adminKey } = req.body;
  if (adminKey !== process.env.ADMIN_SECRET) {
    return res.status(403).json({ error: 'Invalid admin key' });
  }
  const team = await Team.create({ name: teamName });
  const token = jwt.sign({ teamId: team._id }, process.env.JWT_SECRET, { expiresIn: '24h' });
  res.json({ token });
});

Security I Actually Cared About

CTF platforms are a delicious irony: a security-focused event running on infrastructure that gets attacked constantly by the very people you invited. I had to think defensively.

Rate Limiting: Flag submissions are rate-limited to 10 per minute per team. Auth endpoints allow 20 requests per 15 minutes. This prevents brute-force flag guessing cold.

Input Sanitization: All flag and team name inputs are trimmed, length-checked, and passed through Mongoose’s schema validation before touching the database.

CORS Hardening: Only the explicit Render frontend URL is whitelisted. No wildcards.

JWT + No Passwords: There’s nothing to leak. Team auth tokens expire in 24 hours and are re-issued on login.

The flag format was locked to cyberfest{...} to make flag parsing deterministic and prevent ambiguity.


The Frontend: Cobalt Blue Aesthetic

The frontend is intentionally vanilla HTML/CSS/JS—no React, no build step, no bundler. This was a deliberate choice: simplicity of deployment. A static site on Render deploys in under 60 seconds.

The visual theme is Cobalt Blue on Black with White accents. It reads as “hacker aesthetic” without being a tired green-on-black terminal cliché.

Three views:

  • index.html — Main CTF platform (challenges, scoreboard, team management)
  • admin.html — Admin panel (create/edit/delete challenges, view teams)
  • Real-time scoreboard — Updates live via polling, showing ranked teams with solve counts, scores, and timestamps of last correct submission.

The Seed Script Problem

121 challenges. Each with a title, description, difficulty, category, point value, flag, and optional file URL. Entering this manually via the admin panel would have taken days and been catastrophically error-prone.

I wrote backend/seed.js. One npm run seed command populates the entire database from a structured JSON definition. Idempotent — can be re-run safely. This turned what would have been a multi-day data entry job into a two-second database operation.

For a national-level event where challenge integrity is non-negotiable, this was not optional — it was the only sane path.


Deployment Architecture on Render

Render (Free Tier)
├── Web Service     ← Node.js backend  (root: /backend, start: npm start)
└── Static Site     ← HTML frontend   (root: /frontend, no build step)

MongoDB Atlas
└── Free Cluster    ← Mongoose models (Team, Challenge, Submission)

Environment variables: MONGODB_URI, JWT_SECRET, ADMIN_SECRET, FRONTEND_URL. That’s it. The entire secret surface area of the platform is four env vars.


36 Hours: Monitoring the Live Event

Building the platform was one thing. Being on-site as the sole monitor for a 36-hour live CTF was a completely different kind of pressure.

My responsibilities during the event:

  • Flag dispute resolution — when a team claimed their exact flag was correct but the system rejected it (usually a trailing space or encoding issue), I had to investigate and adjudicate in real time
  • Infrastructure babysitting — watching Render logs for abnormal submission spikes, keeping an eye on MongoDB Atlas cluster metrics
  • Rate limit tuning — I had to tighten the flag submission rate limit mid-event because one team was hitting it hard with scripted brute-force attempts
  • Challenge clarifications — fielding questions from teams on Discord about ambiguous challenge descriptions, issuing clarification edits via the admin panel live
  • Scoreboard integrity — manually verifying the top-5 scoreboard before announcing winners to ensure no submission anomalies

The platform held for all 36 hours. Zero downtime. Zero flag leaks. The Render free tier managed the load fine because I’d been careful with connection pooling and had index coverage on every query the scoreboard used.

The hardest part wasn’t technical — it was staying sharp on hour 30. That’s when the most intense scoreboard drama happened.


What I’d Do Differently

First-blood tracking. The first team to solve a challenge deserves recognition on the UI—a small badge or highlight. I didn’t build this and I wish I had.

WebSocket scoreboard. I used polling for the live scoreboard. With a bit more time, a proper WebSocket connection (socket.io) would eliminate even the 5-second lag.


The Challenge Generation Scripts

One thing I built that saved me enormous time: two Python scripts that live in the repo root.

generate_challenges.py — takes a structured definition of each challenge (title, description, difficulty, flag, category) and programmatically builds out the challenge objects ready to pipe into the seed script. It enforces validation on every challenge: no duplicate flags, no missing fields, no flag format violations. Running it gives you instant feedback if any challenge is malformed.

generate_apk_challenges.py — specifically for generating the Android APK reverse-engineering challenges. It wraps real Android APK manipulation steps, embeds flags inside compiled bytecode or resource files, and packages the result. Reverse-engineering 5 different APKs at varying levels of obfuscation was one of the most technically demanding parts of the challenge set to build.

Both scripts were the answer to: how do you maintain 121 challenges without losing your mind? The answer is: you treat challenges as data, not as manual entries.

What the Teams Actually Found Hard

Post-event solve rate analysis across all 121 challenges:

Category Solve Rate Notes
Easy (warmup 1–3) 95%+ Designed accessible — most teams cleared these first
Medium Forensics/Stego 40–60% Required tools (Autopsy, Steghide, ExifTool)
Hard Reversing 15–25% Ghidra/Binary Ninja players progressed; others skipped
Insane Crypto <10% Two teams solved all. Everyone else didn’t touch them
APK Reversing 10–20% More teams had Android skills than I expected
AI / Prompt Injection 25–35% Novel category — teams genuinely enjoyed it
Web Exploitation 20–35% Classic SQLi/XSS/IDOR — solid mid-tier solves
Master Web (MW1–MW11) <5% One team solved MW1. Nobody got past MW4
OSINT 30–40% Teams without a dedicated OSINT player skipped it entirely

The Master Web challenges were the talk of the event. Flags were hidden inside a live deployed React site — inside service worker pre-cache configs, invisible CSS-styled console output, JSON-LD schema blocks, and WebAssembly block comments. Nobody expected flags to live there.


Running a national-level cybersecurity conclave as VP of DDC — coordinating workshops during the day, then switching into platform builder and live monitor mode for a 36-hour CTF — was the most operationally intense thing I’ve done as a student.

Building the platform solo, watching 100+ teams hammer it live, and seeing the scoreboard flip in real time across 121 challenges — no downtime, zero flag leaks, zero auth bypasses — that’s the kind of stress-test that makes you quietly proud.

CyberFest 2026 was a statement: CBIT students can run national-level security events end-to-end. The Digital Defence Club will be back.

— Vasanth