Files
HIPCTF2/docs/database/challenges.md
T

115 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: database
title: Challenge Tables
description: category, challenge, challenge_file, and solve tables — how CTF challenges and scoring are stored.
tags: [database, challenge, category, solve]
timestamp: 2026-07-22T20:30:00Z
---
# Tables
## `category`
| Column | Type | Description |
|----------------|---------|------------------------------------------------------------------------------|
| `id` | TEXT PK | UUID v4. |
| `system_key` | TEXT | Non-null for system categories (e.g. `CRY`, `MSC`, `PWN`, `REV`, `WEB`, `HW`); unique where NOT NULL; `NULL` for user-created categories. |
| `name` | TEXT | Display name (e.g. `Cryptography`). |
| `abbreviation` | TEXT | Short label (e.g. `CRY`), 26 chars, server-uppercased. Globally unique — enforced by `uq_category_abbreviation`. |
| `description` | TEXT | Optional description. |
| `icon_path` | TEXT | Default icon URL (e.g. `/uploads/icons/CRY.png`). |
| `created_at` | TEXT | ISO 8601 timestamp the row was created. |
| `updated_at` | TEXT | ISO 8601 timestamp the row was last updated. |
System rows are seeded by the
`UpdateSystemCategoryKeys1700000000300` migration so the canonical
keys are `CRY` (Cryptography), `MSC` (Misc), `PWN` (Binary
exploitation), `REV` (Reverse Engineering), `WEB` (Web), and `HW`
(Hardware). The forward-only
`RepairCategorySchemaAndSystemCategories1700000000400` migration
repairs legacy schemas on existing databases.
## `challenge`
| Column | Type | Description |
|-------------------|----------|-----------------------------------------------------------------------------------|
| `id` | TEXT PK | UUID v4. |
| `name` | TEXT | Display name. **Case-insensitive uniqueness** enforced by `uq_challenge_name_lower`. |
| `description_md` | TEXT | Markdown description. |
| `category_id` | TEXT | FK → `category.id` (`ON DELETE RESTRICT`). |
| `difficulty` | TEXT | `'LOW'` / `'MEDIUM'` / `'HIGH'`. |
| `initial_points` | INTEGER | Starting point value (1100000). |
| `minimum_points` | INTEGER | Floor after decay (1100000, must be ≤ `initial_points`). |
| `decay_solves` | INTEGER | Solves at which decay reaches the minimum (110000). |
| `flag` | TEXT | Submission string expected from players. **Never returned in non-admin DTOs.** |
| `protocol` | TEXT | `'NC'` (netcat-style connection) or `'WEB'` (browser-based). |
| `port` | INTEGER | TCP port (nullable for WEB, required 165535 for NC). |
| `ip_address` | TEXT | IP the challenge listens on (defaults to `setting.defaultChallengeIp`). |
| `enabled` | INTEGER | Boolean — 1 (default) or 0. |
| `created_at` | TEXT | ISO 8601 timestamp the row was created. |
| `updated_at` | TEXT | ISO 8601 timestamp the row was last updated (bumped on every PUT). |
The `UpgradeChallengeAdminSchema1700000000500` migration rebuilt
the `challenge` table atomically (preserving legacy data), introduced
canonical enums, the `enabled` flag, `updated_at`, the
`LOWER(name)` unique index, and tighter scoring defaults. The
category FK stays at `ON DELETE RESTRICT` so the
`CATEGORY_HAS_CHALLENGES` business rule is preserved.
The `SeedSampleChallenges1700000000600` migration inserts a small
representative set of sample challenges (one or more per canonical
system category) on a fresh database so the `/challenges` board has
cards immediately after bootstrap. It is idempotent: if the
`challenge` table already contains rows (e.g. an admin imported a
custom challenge set), it does nothing. Sample flags follow the
`flag{<token>}` convention and are exposed only inside admin views.
## `challenge_file`
| Column | Type | Description |
|---------------------|---------|--------------------------------------|
| `id` | TEXT PK | UUID v4. |
| `challenge_id` | TEXT | FK → `challenge.id` (`ON DELETE CASCADE`). |
| `original_filename` | TEXT | Filename from upload. |
| `stored_filename` | TEXT | UUID-based filename on disk under `UPLOAD_DIR/challenges/`; unique. |
| `mime_type` | TEXT | Source MIME type (defaults to `application/octet-stream`). |
| `size_bytes` | INTEGER | Decoded file size in bytes. |
| `created_at` | TEXT | ISO 8601 timestamp. |
The upgrade migration added `stored_filename`, `mime_type`,
`size_bytes`, and `created_at`. The challenge → file FK was changed
from `RESTRICT` to `CASCADE` so deleting a challenge in one DB
transaction removes all its `ChallengeFile` rows in lockstep.
## `solve`
| Column | Type | Description |
|------------------|---------|----------------------------------------------------------------------|
| `id` | TEXT PK | UUID v4. |
| `challenge_id` | TEXT | FK → `challenge.id` (`ON DELETE CASCADE`). |
| `user_id` | TEXT | FK → `user.id` (`ON DELETE RESTRICT`). |
| `solved_at` | TEXT | ISO 8601 timestamp the solve was recorded. |
| `points_awarded` | INTEGER | Points actually awarded (after decay). |
| `base_points` | INTEGER | Snapshot of `challenge.initial_points` at solve time. |
| `rank_bonus` | INTEGER | Extra points from rank (e.g. first-blood). |
| `is_first` | INTEGER | Boolean — first-blood flag. |
| `is_second` | INTEGER | Boolean — second-blood flag. |
| `is_third` | INTEGER | Boolean — third-blood flag. |
`uq_solve_challenge_user` on `(challenge_id, user_id)` is retained;
deleting a challenge now cascades to its solve rows, while deleting
a user still requires explicit handling (`user_id` remains
`RESTRICT`).
# Scoring formula
`points_awarded = clamp(initial_points - decay, minimum_points, initial_points)`
where `decay = max(0, base_points - minimum_points) * solvesBefore / decay_solves`,
and additional `rank_bonus` may be added for early solves.
# See also
- [Database Schema Overview](/database/schema.md)
- [System Endpoints](/api/system.md) (SSE scoreboard stream)
- [Scoreboard Stream](/guides/scoreboard-stream.md)
- [Admin — Challenges](/guides/admin-challenges.md)