No description
  • Python 49.8%
  • Dockerfile 46.8%
  • Shell 3.4%
Find a file Use this template
Carlo Field 0f2a3552b4
feat: add sample handout
Signed-off-by: Carlo Field <carlo@swiss.dev>
2026-11-02 18:10:08 +01:00
.github chore: better templating for registries and different challenge forms 2026-11-01 22:44:05 +01:00
handout docs: Use new default flag format 2024-12-15 16:18:39 +01:00
solution expanded README.md and added a README in the solution directory 2024-12-09 16:38:51 +01:00
source chore: add :3 2026-10-17 12:41:18 +00:00
challenge.yaml feat: add sample handout 2026-11-02 18:10:08 +01:00
LICENSE Initial commit 2024-10-09 17:47:15 +02:00
README.md chore: better templating for registries and different challenge forms 2026-11-01 22:44:05 +01:00

universal-challenge-template

A cross-platform universal challenge template for creating jeopardy ctf challenges.

Structure

Folder Purpose
/handout/ Contains all files that should be handed out to the players
/source/ Contains all files required to build and run the dockerized remote
/solution/ Dockerized exploit script and short README with the solution
/metadata.yaml Metadata about the challenge author, description, flag, etc...
/README.md Contains critical information about the deployment of the callenge
/LICENSE License of the challenge

Handout

Public files, to be downloaded by the players. Please do not upload archives, as we expect them to be created by the organizing team and uploaded to the competition platform. For categories where applicable, this folder SHOULD contain a docker-compose.yml to allow locally building and running the challenge. Notice that the folder MUST NOT contain any sensitive information, such as the flag or the solution, if the challenge is remote. If the challenge is local, e.g., a reversing or a forensic challenge, the handout folder MUST contain the necessary files to solve the challenge, including the flag.

Source

Sources of the challenge, including any helper script used to generate or test it. The challenge must be dockerized and must come with a docker compose file (if applicable). The docker compose file should be named docker-compose.yml and should be placed inside this directory. A typical structure for the /source/ folder includes a nested /source/src/ folder with the actual sources of the challenge. Please test it with docker compose (not docker-compose) before submitting the challenge.

The challenge MUST implement the following points:

  • Take the flag from the environment variable called FLAG and store it however it needs to.
  • Make connections to other services or containers configurable. If for example a web challenge also needs access to a database from a different docker image, the web container must allow configuration of both database host and port (for example via env vars DB_HOST and DB_PORT respectively)

Container publishing

The handout is archived and published only when handout/ exists and has entries (including hidden files). It may be a directory symlink, such as handout -> source; the archive includes the target directory's contents. Missing, empty, or dangling directory symlinks skip handout publishing.

The Forgejo build workflow discovers both supported layouts automatically:

Dockerfile Build context Image
source/Dockerfile source/ registry.k8s.swiss.dev/<org>/<repo>/challenge
source/<service>/Dockerfile source/<service>/ registry.k8s.swiss.dev/<org>/<repo>/<service>

Both layouts can be used together, provided each image name is unique. Service directory names must be valid lowercase container image names. A root Dockerfile and source/challenge/Dockerfile would conflict and cause discovery to fail.

Images receive main, latest, and sha-<commit> tags, with a separate buildcache tag for each image. Builds run on pushes to main and v* tags that change the workflow, build scripts, source, or handout. Repositories without a source Dockerfile skip container builds.

For this template, <org>/<repo> is x3ctf/challenge-template. Derived repositories automatically use their own organization and repository name, lowercased. The handout is published alongside the images as /handout:latest. Configure the HARBOR_USER variable and HARBOR_TOKEN secret in Forgejo to authenticate with the registry.

Solution

Contains a fully containerized exploit script that ideally accepts the target host, port and protocol from the command line running the container.

Metadata

Most values in the metadata file are self-explanatory. As general guidelines, please pick a category among the following ones:

  • rev
  • pwn
  • web
  • cry
  • hw
  • misc

If a challenge does not fit into any of these categories, please use misc. If a challenge has multiple categories, please use the one that best fits the challenge. Additional categories can be mentioned in the challenge description.

Flag Format

Flags SHOULD be customizable by each country via the FLAG env variable. When this is not possible due to, e.g., having the flag hardcoded in the binary provided as the handout of a reversing challenge, the flag format MUST be specified in the description of the challenge. The flag itself can include a funny l33t speak message, but ensure to include a randomly generated part as well (typically at the end) to avoid brute-forcing the flag. Additionally, please do not leak hints about the intended solution in the flag content: if the challenge has a trivial bypass, we can still release a fixed version of the challenge that requires the intended solution to be exploited.

In case its not possible to create a flag that can be dynamically generated (such as in forensics, misc or certain reverse engineering challenges), please use the following flag prefix: dach2026{...}

For rev challenges, where the flag can be retrieved from the handout, do not use dynamic or customizable flags, because then players can obtain multiple builds of a challenge with different flags (e.g. by downloading the challenge from different countries). Comparing the challenge files will then act as a side channel to figure out where the flag is encoded.

Difficulty Levels

Although the scoring system will be implemented independently by each country, it is good to know the expected difficulty level for each challenge. Publishing the difficulty level in the metadata file will also help players to choose the challenges they want to solve.

The difficulty level should be one of the following:

  • easy: the challenge is designed to be solved by most of the participants and does not require extensive background on the topic. Still, players are required to know what they are doing and not obtain the flag by accident (e.g., guessing, blind brute-forcing, inserting a long sequence of As).
  • medium: the challenge is designed to be solved by most participants with some background in the field who are willing to invest time in solving the challenge. Ideally, up to half of the participants who can solve easy challenges should be able to solve medium challenges.
  • hard: an expert player is supposed to solve this challenge in a few hours. The challenge requires a deep understanding of the topic and should require some research to be solved. Ideally, up to half of the participants who can solve medium challenges should be able to solve hard challenges.

The CTF duration is 48 hours.