Skip to content

Challenge Specification v0.1

Challenge Folder Structure

.
└── 📁 {challenge_name}/
    ├── 📁 src/
    │   └── 📄...
    ├── 📁 dist/
    │   └── 📄...
    ├── 📁 service/
    │   ├── 📁 {service_name}/
    │   │   ├── 📄...
    │   │   └── 🐋 Dockerfile
    │   └── 🐋 compose.yml (optional)
    ├── 📁 solution/
    │   └── 📄...
    ├── 📄 chall.toml
    └── 📄 README.md
File/Directory Description
src/ Directory containing the source files for the challenge. This is used to assist testing, not for giving to users attempting the challenge.
dist/ Directory containing the challenge files to give to users attempting the challenge.
service/ Directory containing the services for challenges that require hosting.
service/{service_name}/ Directory containing the files for the service. This folder must contain a Dockerfile
service/compose.yml Docker Compose file to run the services. Useful if extra configurations in the compose file is needed for the challenge to work. This file will be added directly to the root compose file in the repo using the include element.
solution/ Directory containing the solution files for the challenge. This is used to assist testing, not for giving to users attempting the challenge.
chall.toml TOML file containing the metadata for the challenge. This is generated automatically.
README.md Markdown file containing a text summary of the challenge's details. This is generated automatically.`

Example

# Challenge Metadata File (version 0.1)
# This file is machine generated. DO NOT EDIT unless you know what you are doing.
# If you want to create or edit a challenge, use the CLI instead.

# ------------------- Challenge Specification Version (required) -------------------
version = "0.1"


# --------------------------- Challenge Info (required) ----------------------------
# This section specifies the information about the challenge.
# The section must have the following fields:
# - author: The author of the challenge
# - category: The category of the challenge
# - description: The description of the challenge
# - difficulty: The difficulty of the challenge
# - name: The name of the challenge
[challenge]
author = "Author Name"
category = "category"
description = "Description"
difficulty = "difficulty"
name = "Challenge Name"

# ------------------------ Challenge Folder Name (optional) ------------------------
# Specifies the folder name to use to store the challenge files.
# If not specified, the folder name will be generated from the challenge name.
# Must be a valid folder name, restricted to the pattern \^[a-zA-Z][a-zA-Z0-9 _-]*$\

folder_name = "folder_name"


# --------------------------- Challenge Files (optional) ---------------------------
# Specifies the files that are part of the challenge.
# The files are specified as a list of strings, where each string is a path to a file.
# The paths are relative to the challenge directory.
# Alternatively, specify a URL to download the file from the internet.

files = [
    "file1",
    "file2"
]

# ----------------------- Challenge Requirements (optional) ------------------------
# Specifies the challenges that must be solved before this challenge can be unlocked.
# The requirements are specified as a list of strings, where each string is the name of a challenge.

requirements = [
    "challenge1",
    "challenge2"
]


# ------------------------ Challenge Extra Info (optional) -------------------------
# This section specifies extra information about the challenge.
# The fields in this section must be specified if it is required by the ctf_config.toml file

[challenge.extras]
discord = "a_discord_username"


# --------------------------- Challenge Flags (required) ---------------------------
# Specifies the flags that are part of the challenge.
# To specify multiple flags, create another table in the array of tables.
# The flag table must have the following fields:
# - flag: The flag value
# - regex (optional): If the flag is a regex flag, otherwise it is a static flag. Defaults to false
# - case_insensitive (optional): If the flag is case insensitive or not. Defaults to false

# Static case-sensitive flag
[[challenge.flags]]
flag = "flag{example}"

# Static case-insensitive flag
[[challenge.flags]]
flag = "flag{example2}"
case_insensitive = true

# Regex case-sensitive flag
[[challenge.flags]]
flag = "flag{example3}"
regex = true

# Regex case-insensitive flag
[[challenge.flags]]
flag = "flag{example4}"
regex = true
case_insensitive = true


# --------------------------- Challenge Hints (optional) ---------------------------
# Specifies the hints that are part of the challenge.
# To specify multiple hints, create another table in the array of tables.
# The hint table must have the following fields:
# - cost: The cost of the hint
# - content: The content of the hint
# - requirements (optional): The requirements to unlock the hint. This should be
#   an index referencing a hint in the array of tables. Defualts to null

# Example hint
[[challenge.hints]]
cost = 10
content = "This is a hint"

# Example hint with requirements
[[challenge.hints]]
cost = 20
content = "This is a hint with requirements"
requirements = 0


# ------------------------- Challenge Services (optional) --------------------------
# Specifies the services that are part of the challenge.
# To specify multiple services, create another table in the array of tables.
# The service table must have the following fields:
# - name: The name of the service. This will be the name of the docker container. Restricted to [a-z0-9_-] and must be unique
# - path: The path to the service's directory. This path is relative to the challenge directory
# - port: The port that the docker container exposes. This is an integer. Only one of port or ports should be specified
# - ports: The ports that the docker container exposes. This is an array of integers. Only one of port or ports should be specified
# - type: The type of the service. This must be one of the following:
#   - web      : A web service, must have a port exposed
#   - tcp      : A tcp service, must have a port exposed
#   - ssh      : An ssh service, must have a port exposed
#   - secret   : A secret service, it must have a port exposed, but will not be shown in the challenge info
#                This is useful for challenges where the service must be discovered by the player
#   - internal : An internal service, does not need to expose a port, and will not be shown in the challenge info
#                This is useful for challenges where the service should not be accessed directly, i.e. web admin bots

# Example service
[[challenge.services]]
name = "service1"
path = "service1"
port = 1337
type = "web"

# Extra service info (optional)
[challenge.services.extras]
privileged = true  # Make the docker container privileged

Fields

author

Defines the author of the challenge. Must be a string.

author = "Author Name"

category

Specifies the category of the challenge. This should be a lowercase string, and must be located in the categories field in the CTF repository config.

category = "category"

description

Description of the challenge. This can be a multi-line string.

description = "Description"

description = """This is a multi-line description
of the challenge"""

difficulty

Specifies the difficulty of the challenge. This should be a lowercase string, and must be located in the difficulties field in the CTF repository config.

difficulty = "difficulty"

name

Name of the challenge. If the folder_name field is not provided, this will be used to generate the folder name for the challenge by substituting using the regex expression /^[^a-zA-Z]+|[^a-zA-Z0-9 _-]/.

name = "Challenge Name"

files (optional)

List of files to give users attempting the challenge. Each item in the list can either be a path or a URL. Paths must be relative to the challenge directory.

files = [
    "http://example.com/file1",
    "./dist/file2"
]

requirements (optional)

List of challenge names for challenges that must be completed before this challenge can be attempted.

requirements = [
    "challenge1",
    "challenge2"
]

folder_name (optional)

Specify the folder name for the challenge. Must follow the pattern /^[a-zA-Z][a-zA-Z0-9 _-]*$/. This is useful if you want to have a challenge name that cannot be converted to a folder name. (e.g. 🦈 -> [empty_string])

folder_name = "challenge_name"

extras (optional)

Extra information about the challenge. Extra fields specified in the CTF repository config must be specified here.

[challenge.extras]
discord = "foobar"

flags

List of flags for the challenge. Apart from the flag field, the following fields can be optionally specified:

  • regex (Boolean): Whether the flag is a regex expression. Default is false.
  • case_insensitive (Boolean): Whether the flag is case-insensitive. Default is false.

Examples:

# Static case-sensitive flag
[[challenge.flags]]
flag = "flag{example}"

# Static case-insensitive flag
[[challenge.flags]]
flag = "flag{example2}"
case_insensitive = true

# Regex case-sensitive flag
[[challenge.flags]]
flag = "flag{example3}"
regex = true

# Regex case-insensitive flag
[[challenge.flags]]
flag = "flag{example4}"
regex = true
case_insensitive = true

hints (optional)

List of hints for the challenge. Each hint must have the following fields:

  • cost (Integer): The cost of the hint
  • content (String): The content of the hint
  • requirements (Integer) (Optional): The index of the hint that must be purchased before this hint can be purchased.

Examples:

# Example hint
[[challenge.hints]]
cost = 10
content = "This is a hint"

# Example hint with requirements
[[challenge.hints]]
cost = 20
content = "This is a hint with requirements"
requirements = 0

services (optional)

List of services for the challenge. A service is allows you to specify configurations for a docker container that will be used to host the challenge.

Examples:

# Example service
[[challenge.services]]
name = "service1"
path = "./service/service1"
port = 1337
type = "web"

# Example service with multiple ports
[[challenge.services]]
name = "service2"
path = "./service/service2"
ports = [1337, 1338]
type = "nc"

# Example service with extras
[[challenge.services]]
name = "service3"
path = "./service/service3"
port = 1337
type = "web"

[challenge.services.extras]
privileged = true

Service Fields

name

The name of the service. Must be a string.

name = "service1"

path

Path to the service directory, relative to the root of the challenge folder. Must be a string.

path = "./service/service1"

port (optional)

Port the docker container exposes. Must be specified unless ports is specified or type is "internal". Must be an integer.

port = 1337

ports (optional)

List of ports the docker container exposes. Must be specified unless port is specified or type is "internal". Must be a list of integers.

ports = [1337, 1338]

type

The type of service. Must be one of the following:

  • web : A web service, must have a port exposed
  • nc : A netcat service, must have a port exposed
  • ssh : An ssh service, must have a port exposed
  • secret : A secret service, it must have a port exposed, but will not be shown in the challenge info. This is useful for challenges where the service must be discovered by the player
  • internal: An internal service, does not need to expose a port, and will not be shown in the challenge info. This is useful for challenges where the service should not be accessed directly, i.e. web admin bots
type = "web"

extras (optional)

Extra configurations for the service to be passed to the docker compose file.

Warning

The configurations set here will not overwrite configurations generated by ctf-architect.

The following service configuration:

[[challenge.services]]
name = "service1"
path = "./service/service1"
port = 1337
type = "web"

[challenge.services.extras]
privileged = true

Will look similar to this in the generated docker compose file:

networks:
  challengename-network: {}

services:
  challenge_name-service1:
    build: ./service/service1
    container_name: challenge_name-service1
    networks:
    - challengename-network
    ports:
    - 8000:1337
    privileged: true
    restart: always