PHP · MySQL · Docker · Cloudflare · Security
OutRun CTF
A self-hosted capture-the-flag training platform where users register, work through five security challenges of increasing difficulty, and track their progress on a live dashboard and leaderboard — built, secured, and deployed end to end on my own hardware.
The landing page — beginner-friendly framing, hints, and a leaderboard to climb.
Overview
OutRun CTF is a multi-page web application that simulates a simplified capture-the-flag training environment. Users register, log in, and work through five challenges covering common introductory security concepts — from inspecting hidden HTML to reading HTTP response headers. A dashboard tracks each user's points, solve rate, and global rank in real time.
The goal wasn't just to build a working app, but to demonstrate the full set of concepts behind a real dynamic web application: authentication, session management, secure database interaction, and a deliberately structured codebase — not just a collection of scripts that happen to work.
Registration and login, backed by hashed credentials and server-side sessions.
Application security
Security was treated as a first-class requirement, not an afterthought — every form and every user input is handled with a specific attack in mind.
- Passwords hashed with bcrypt via
password_hash(), verified withpassword_verify() - All queries run through PDO prepared statements to prevent SQL injection
- Output escaped with
htmlspecialchars()to prevent XSS - CSRF tokens on every form — login, registration, and flag submission
- Session ID regenerated with
session_regenerate_id()after login to prevent session fixation - Flag comparison uses
hash_equals()for constant-time comparison, never== - Rate limiting on login and flag submission (e.g. 10 wrong attempts per challenge per 5 minutes)
- Flags are validated and stored server-side only — never exposed in page source or client-side JS
The dashboard — points, rank, a live leaderboard, and per-challenge status.
Dashboard & progress tracking
After logging in, users land on a dashboard that pulls everything dynamically from the database — no hardcoded stats. It's built to give an at-a-glance read on where someone stands, both against themselves and against everyone else.
- Total points, challenges solved, and total attempts
- A progress bar and challenge grid showing solved vs. unsolved
- Global rank and a live leaderboard
Each challenge presents a problem, a flag submission form, and a hint on request.
Five challenges, five techniques
Each challenge teaches a specific, real-world reconnaissance or inspection skill. Submitted flags are validated server-side, and a correct submission is recorded against that user's account.
- What Lies Beneath (easy) — finding a flag hidden in an HTML comment
- No Robots (easy) — reconnaissance via
robots.txt - Sweet Tooth (easy) — inspecting browser cookies
- Encoded Secrets (medium) — decoding a Base64-encoded flag
- Hidden in Transit (medium) — reading HTTP response headers
Architecture
The application follows an MVC structure. The Model layer handles all database interaction through PDO with prepared statements. The View layer manages the interface, built on a consistent design system of custom CSS variables rather than a framework. The Controller layer processes user input — form submissions, flag checks, session state — and ties the two together.
The database is normalized across six tables: users, challenges, categories, challenge_categories, user_challenges, and countries — separating user data, challenge content, categorization, and per-user progress so each piece can grow independently.
Entity-relationship diagram of the six-table schema.
Containers running on the home server, confirmed via docker ps — the CTF app and database alongside other self-hosted projects.
Self-hosted infrastructure
OutRun CTF isn't hosted on a cloud VM — it runs on a Dell OptiPlex 390 I set up as a dedicated home server, upgraded with additional RAM and wired directly into the network. The whole deployment is managed through Docker.
- Ubuntu Server with Docker and Docker Compose, containers set to
restart: unless-stopped - Docker enabled to start on boot, verified after a full reboot test
- Nginx reverse proxy in front of the app containers
- Deploys by pulling from GitHub and running
docker compose up -dover SSH - Blog posts published through the same headless SSH-key setup, no GUI required
Cloudflare in front of the domain — DNS, proxying, and SSL.
Networking & domain
Running on a home connection means the network layer needed as much attention as the app itself: a stable path from a public domain name down to a single machine on a residential IP.
- Registered domain, DHCP reservation for a stable local IP, ports 80/443 forwarded to the server
- Cloudflare in front of the domain for DNS, free SSL, and DDoS protection — and to keep the home IP hidden
- A DDNS script keeps Cloudflare's DNS record updated automatically if the home IP changes
- Self-signed certificate generated with OpenSSL and configured in Apache, with Cloudflare set to Full (not Flexible) SSL mode end-to-end
Operations & hardening
Once the app was live, the focus shifted to keeping it that way — locking the server down, watching for downtime, and checking the site the way an attacker would rather than assuming it was secure.
- UFW firewall restricted to ports 22, 80, and 443, with Cloudflare's IP ranges whitelisted
- fail2ban running against brute-force attempts
- Docker containers confirmed not running as root
- Apache and PHP version headers disabled (
ServerTokens Prod,expose_php = Off), with generic error messages in production - Custom 404 page styled to match the site
- Scanned the live site with OWASP ZAP, Nikto, and WhatWeb to check for vulnerabilities and exposed technologies
- Automated MySQL backups via cron job
- Uptime Robot monitoring the domain with email alerts on downtime
- Sitemap submitted to Google Search Console
Security research & writing
Alongside the platform itself, I wrote a set of technical posts covering the concepts the challenges are built on:
- An introduction to Nmap and network scanning fundamentals
- Reconnaissance techniques for web applications
- Core networking concepts every security learner should know
- Setting up a personal penetration-testing lab
Outcome
The finished platform meets the full scope of the original proposal: secure authentication, a normalized database, an MVC codebase, five working challenges, and live progress tracking — running end to end on hardware I set up and manage myself, rather than a managed cloud platform. Handling the server, the network, and the security hardening alongside the application code meant thinking about the project as a whole system, not just the parts that live in a code editor.