Skip to content

Writing challenges

The challenge format we use is based on a subset of the compose specification (the format docker compose uses). As such, writing challenges should feel intuitive if you've used Docker Compose before.

Your challenges should be placed in the challs subdirectory of the CTF repository. The challenge is described by a docker-compose.yml or docker-compose.yml.plftera (See the section on the preprocessor for more details).

This document will not describe everything Docker Compose offers, but will instead focus on the differences and extensions our format has.

First: Services and Volumes are the only supported objects. Networks, Secrets and Configs will be ignored.

Defining metadata

The platform extends the compose format with custom metadata that defines your CTF challenge.

To do this, it adds a new key x-ctf-metadata to the compose file. The metadata is structed like this:

YAML
x-ctf-metadata:
    name: My challenge name
    authors:
        - Author 1
        - Author 2
    description_md: |
        In this challenge, you're supposed to find the flag.

        Check [CTFTime](https://ctftime.org/) to learn more.
    flag_validation_fn:
        // Optional, you can call setFlagValidationFunction with an arbitrary function (flag: string) => boolean that validates a flag and returns true if it's valid
        // You MUST either define a flag validation function or a flag
        setFlagValidationFunction((flag) => {
             return flag.startsWith("PLFANZY{") && flag.endsWith("}");
       });
    flag: PLFANZEN{MY_FLAG}
    categories: # Should only contain categories defined in the event metadata
        - misc
        - rev
    attachments:
        - path/to/your/file # Path, relative to the compose file
    release_time: Option<u64>
    end_time: Option<u64>
    auto_publish_src: true # Optional, defaults to false, more details in the handout section
    difficulty: hard # Should be a difficulty defined in the event metadata
    data_pvc_size: 100Mb # More details later
    additional_metadata: # Additional data you can freely put anything in, optional - May be useful for your points calculation

Unless explained otherwise by a comment, fields are required.

The data PVC

If you use relative mounts to your compose file in any service, where you mount a subdir of (./data) a data PVC will be created. It contains everything you put into that data directory.

service properties

The following table gives you information on which properties are and aren't supported.

KeySupport stateNotes
attachIgnoredNot relevant on Kubernetes
buildNot supportedPlanned for the future
blkio_configNot supported
cpu_countSupported
cpu_percentNot supported
cpu_sharesNot supported
cpu_periodNot supported
cpu_quotaNot supported
cpu_rt_runtimeNot supported
cpu_rt_periodNot supported
cpusSupported
cpusetNot supported
cap_addSupportedIf this is used, a VM will be created instead of a container. You will have these capabilities within the VM.
cap_dropSupported
cgroupNot supported
cgroup_parentNot supported
commandSupported
configsNot supported
container_nameNot supported
credential_specNot supportedMostly for Windows containers, which we don't have at the moment
depends_onIgnoredWe can not enforce startup order easily in Kubernetes
deployPartially SupportedOnly replicas and labels are supported
developIgnoredNot relevant on Kubernetes
device_cgroup_rulesNot supported
devicesNot supportedCurrently, disabled for security reasons
dnsSupported
dns_optSupported
dns_searchSupported
domain_nameSupported
entrypointSupported
env_fileSupportedFiles must be inside the working directory
environmentSupported
exposeSupported
extendsNot supported
annotationsSupportedMapped to Pod annotations (not Deployment)
external_linksNot supported
extra_hostsSupported
group_addSupported
healthcheckNot supportedWe can consider adding this later
hostnameSupported
imageSupported
initSupportedUses tini via init container + wrapper
ipcNot supported
utsNot supported
isolationNot supportedThis is mostly for Windows containers
labelsSupportedMapped to Pod labels (not Deployment)
linksNot supported
loggingNot supported
network_configNot supported
mac_addressNot supported
mem_limitSupported
mem_reservationSupported
mem_swappinessNot supported
memswap_limitNot supported
oom_kill_disableNot supported
oom_score_adjNot supported
pidNot supported
pids_limitNot supportedThis is a node-level setting in Kubernetes, not per-container
platformNot supported
portsSupported
privilegedSupportedCreates a VM instead of a real container. You don't have access to host devices, but everything else you would have in a privileged container.
profilesNot supported
pull_policySupportedBuild is currently not supported, like build, it is planned for the future
read_onlySupported
restartIgnoredKubernetes only supports Always for Deployments
runtimeSupportedIf the container uses privileged or certain capabilities, Kata Containers will be used instead of this
scaleSupported
secretsNot supported
security_optNot supported
shm_sizeSupported
stdin_openSupported
stop_grace_periodSupported
stop_signalSupported
storage_optNot supported
sysctlsNot supported
tmpfsSupported
ttySupported
ulimitsNot supported
userPartially SupportedOnly user IDs are supported, not usernames
userns_modeNot supportedFor security reasons
volumesPartially SupportedOnly tmpfs, bind mounts inside the working directory, and named volumes are supported
volumes_fromNot supported
working_dirSupported

Network policies

In addition, we declare the x-ctf-network-policy on services.

As soon as you add a policy, all traffic not permitted will be dropped.

A network policy looks like this:

YAML
services:
    main:
        image: ubuntu:24.04
        x-ctf-network-policy:
            # Incoming policies are currently not supported
            outgoing:
                rules:
                    - other_party: ClusterDNS # One of Challenge, ClusterDNS, World
                      ports: # Optional
                        - port: 53
                          protocol:
                            - UDP
                            - TCP

volume properties

For volumes, we don't support most properties. The only way to define volumes is the following format:

YAML
volumes:
    volname:
        x-size: 50Mi # The size of the generated volume

Other properties apart from x-size are ignored.

Imprint