Getting Started
How to Play
New to the competition? Here's everything you need to go from a DEF CON badge to your first patched challenge.
The loop
find the flaw → patch it → open a PR → CI scores it
There are no flags to submit. Every challenge is scored by an automated regression test that only passes once the vulnerability is actually fixed.
- 1
Get your DEF CON badge
This is an in-person competition at the Las Vegas Convention Center. A valid DEF CON 34 badge gets you into the OWASP CTF area.
- 2
Sign in with GitHub
Use the sign-in button in the header. Your GitHub login is how the leaderboard and your profile track your progress. The scorer credits points to the account that authors the pull request, so play from the same account you sign in with.
- 3
Pick a target and a challenge
Browse the six vulnerable apps on the Challenges page: Juice Shop, DVWA, WebGoat, Security Shepherd, VulnerableApp, and VAmPI. Each has dozens of independent challenges at different difficulty levels; pick any one to start.
- 4
Find the vulnerability
Work the target like a real audit: read the source, exercise the app, and identify the OWASP Top 10 flaw behind the challenge. Using AI tools to help analyze and remediate is part of the intended workflow, so use them if it helps.
- 5
Patch it and open a pull request
Fork the target's repo under the OWASP-CTF org, fix the vulnerability on a branch in your fork, and open a PR back against the repo's dc34-ctf branch. This is secure development practice, not flag hunting. The fix itself is the deliverable.
- 6
Get scored automatically
A GitHub Action builds your patched app and runs the full regression suite against it. Every passing challenge test scores its points immediately: no manual grading, no waiting on an organizer. Pushing more fixes to the same PR re-scores it.
Worked example
Your first patch, end to end
Here’s the whole loop on a real challenge: Login Admin in Juice Shop, a classic SQL injection. Follow it verbatim to land your first points and see exactly what a scoring run looks like, then repeat the pattern on every other challenge.
- 1
Fork the target and clone your fork
Fork OWASP-CTF/juice-shop on GitHub (or with the gh CLI), then clone it. The default branch, dc34-ctf, is the one the scorer watches.
gh repo fork OWASP-CTF/juice-shop --clone cd juice-shop
- 2
Create a branch for your fix
One branch per fix keeps your PRs clean and easy to re-score.
git checkout -b fix/login-sql-injection
- 3
Find the flaw
The Login Admin challenge (A05: Injection) lives in routes/login.ts. User input is concatenated straight into the SQL string, so an email like ' OR 1=1-- logs in as the first user in the table: the admin.
// routes/login.ts: the vulnerable query models.sequelize.query( `SELECT * FROM Users WHERE email = '${req.body.email || ''}' AND password = '${security.hash(req.body.password || '')}' AND deletedAt IS NULL`, { model: UserModel, plain: true } ) - 4
Patch it
Replace string interpolation with bind parameters. The database driver now treats the email and password strictly as data, so they can never rewrite the query itself.
// routes/login.ts: parameterized fix models.sequelize.query( 'SELECT * FROM Users WHERE email = $1 AND password = $2 AND deletedAt IS NULL', { model: UserModel, plain: true, bind: [req.body.email || '', security.hash(req.body.password || '')] } ) - 5
Commit and push to your fork
Write the commit message like you would on a real security fix: say what was vulnerable and how the patch closes it.
git add routes/login.ts git commit -m "Fix SQL injection in login route with bind parameters" git push -u origin fix/login-sql-injection
- 6
Open the PR against dc34-ctf
The base repo is OWASP-CTF/juice-shop and the base branch is dc34-ctf. The scorer only watches that branch. The GitHub web UI's “Compare & pull request” button works too; just check the base branch.
gh pr create --repo OWASP-CTF/juice-shop --base dc34-ctf \ --title "Fix SQL injection in login route" \ --body "Replaced string-interpolated SQL with bind parameters."
- 7
Watch the scorer do its thing
The ctf-score Action builds your patched app, boots it in a sandbox, and runs the challenge regression suite against it. When it finishes you'll get a “🏁 Score recorded” comment on the PR, and your points appear on the leaderboard and your profile moments later.
Bonus
That one-line fix doesn’t just close Login Admin. The same injection powers the Login Bender and Login Jim challenges, so a single parameterized query scores all three. Real fixes often cascade like this: patch the root cause, not the symptom.
Good to know
- Every push to an open PR re-runs the scorer, and the run evaluates your whole app, so you can keep stacking fixes on one branch or open a fresh PR per fix, whichever you prefer.
- Your best-ever result per challenge is what counts. A later fix always replaces an earlier miss; you can never lose points by trying.
- Points are credited to the GitHub account that authored the PR. Team totals are the sum of what each member lands individually.
How scoring works
Every challenge is worth a fixed number of points based on difficulty, and harder vulnerabilities pay out more. Points are awarded the moment your PR’s regression test passes, and your best-ever result for each challenge is what counts, so a later fix always replaces an earlier miss. Your live total, per-app breakdown, and patched and non-patched counts are visible on your profile once you’re signed in.