Home

Awesome

mkCTF

Why ?

This framework aims at helping your team create jeopardy CTF challenges using a configurable structure which will enable efficient integration and deployment on the CTF infrastructure.

This project was initially created for managing challenges for INS'hAck 2017.

You can find challenges and writeups of the past editions of INS'hAck in this repository.

This project evolved over time to enable even more automation when deploying challenges on a Rancher-based infrastructure.

Caution

Your mkCTF repository shall remain private until the CTF event ends. It stores flags in plaintext to allow CTF admins to rebuild challenges from sources without generating new flags. It is recommended to enable 2FA add defensive layers to your repository. Do not generate or manage read-only access token lightly. Do not give access to your repository to untrusted users.

Dependencies

This project requires Python 3.9+ and has been designed and tested on Linux only. It might work on Darwin and Windows Subsystem for Linux as well.

Other dependencies will depend on the challenges themselves.

Setup

You can setup mkCTF in a few steps:

curl -o setup.sh https://raw.githubusercontent.com/koromodako/mkctf/master/setup.sh
chmod +x setup.sh
./setup.sh
rm setup.sh

Then create your first CTF:

mkdir example-ctf && cd example-ctf
mkctf-cli init
# then simply follow the terminal prompts

Tools

Each tool description may refer to concepts defined in Concepts section so make sure to check it out if you encounter an new concept.

mkctf-cli

mkctf-cli helps you and CTF co-authors to manipulate a mkCTF repository. It ensures that challenges integration with the CTF infrastructure won't give you a headache.

mkctf-cli -h screenshot

You can enumerate challenges to have a quick overview of the work progression and distribution across tags and categories.

mkctf-cli enum screenshot

You can also export public files of your challenges in a single command. Public files location in a challenge directory can be configured.

mkctf-cli enum screenshot

mkctf-monitor

mkctf-monitor is an all-in-one monitoring solution running healthchecks on a regular basis and sending reports to the dashboard using an HTTP API defined in Dashboard section.

mkctf-monitor -h screenshot

Once you have initialized your mkCTF repository, you can build a monitoring image and run it by following this procedure:

cd example-ctf
mkdir monitoring/ctf
cp -r .mkctf challenges monitoring/ctf
sudo DOCKER_BUILDKIT=1 docker build -t example-ctf-monitoring:1.0.0 .
sudo docker container run --rm \
                          --env monitoring.env \
                          --name monitoring \
                          example-ctf-monitoring:1.0.0

Concepts

Challenge

Challenges are the base of jeopardy CTF. Each challenge can be described using some metadata:

# challenge author to be displayed on the dashboard
author: someone
# category in terms of infrastructure requirements
#  - `simple` means that the challenge is static (downloadable elements only)
#  - `server` means that the challenge requires a server and server can be shared
#    between all players, challenge vulnerabilities allow read-only access at most
#  - `sandbox` means that the challenge can be destroyed/altered by a player and
#    shall be allocated on a per team or player basis
category: simple
# challenge estimated difficulty to be displayed on the dashboard
difficulty: hard
# enabled is set to true when the challenge is considered production-ready:
#  - it runs
#  - it is stable
#  - it has been integrated successfully with the CTF infrastructure
#  - it has been tested successfully (at least manually)
enabled: false
# challenge flag to be found by the player and to be used when building or
# health-checking, it can be set manually or generated automatically
flag: INSA{Th1s_Is_N0t_A_R34L_flag;)}
# challenge logo to be displayed on the dashboard
logo_url: ''
# display name of the challenge
name: My New Challenge
# number of points to be awarded when the challenge is solved. `-3` is a specific
# value meaning that the number of points must be calculated dynamically for this
# challenge
points: -3
# slug of the challenge
slug: my-new-challenge
# static url of the challenge archive. It is generated automatically from repo
# `salt` and `base_url`
static_url: https://static.ctf.insecurity-insa.fr/9afb7029e93ed50c280c69c8443418c5683d05f8.tar.gz
# tags are chosen by the author to specify the nature the challenge
tags:
- forensics

Repository

A repository contains a collection of challenges and monitoring resources. It can described using some metadata:

general:
  # difficulties holds a list of difficulties which can be used to describe a
  # challenge complexity
  difficulties:
  - very easy
  - easy
  - medium
  - hard
  - insane
  # docker holds the information related to the registry where challenge images
  # will be stored.
  docker:
    registry: registry.example.ctf
    user: examplectf
  # domain holds the parent domain of all challenges
  domain: example.ctf
  # flag holds the prefix and suffix to be used for challenges
  flag:
    prefix: ECTF{
    suffix: '}'
  # tags holds a list of tags available to categorize challenge content
  tags:
  - for
  - rev
  - pwn
  - web
  - web3
  - prog
  - osint
  - radio
  - crypto
  - mobile
  - bounty
  - stegano
# categories holds the list of files and directories to be created for each
# infrastructure category (sandbox, server, simple)
categories:
  sandbox:
    dirs:
      private:
      - server
      public: []
    files:
    - exec: false
      from: Dockerfile.sandbox-server
      name: server/Dockerfile
    - exec: false
      from: Dockerfile.server
      name: server/Dockerfile.sandbox
    - exec: false
      from: banner
      name: server/banner
    - exec: false
      from: sshd_config
      name: server/sshd_config
    - exec: true
      from: sandbox_start.sh.jinja
      name: server/sandbox_start.sh
  server:
    dirs:
      private:
      - server
      public: []
    files:
    - exec: false
      from: Dockerfile.server
      name: server/Dockerfile
  simple:
    dirs:
      private:
      - private
      public: []
    files: []
# display name of the jeopardy CTF
name: ExampleCTF
# standard holds the common files and directories to be created for each
# challenge
standard:
  build:
    exec: true
    from: build.jinja
    name: build
  deploy:
    exec: true
    from: deploy.jinja
    name: deploy
  description:
    exec: false
    from: description.md.jinja
    name: description.md
  dirs:
    private: []
    public:
    - public
  files:
  - exec: false
    from: null
    name: .gitignore
  - exec: false
    from: writeup.md.jinja
    name: writeup.md
  - exec: false
    from: healthcheck.deps
    name: healthcheck.deps
  healthcheck:
    exec: true
    from: healthcheck.jinja
    name: healthcheck
# static holds information about which domain will be used to serve each
# challenge archive (exported using `mkctf-cli export /tmp/export-example`)
# and a salt to make archive name impossible to predict
static:
  base_url: https://static.example.ctf
  salt: # hex-encoded bytes (randomly generated)

Standard Programs

mkCTF framework defines some mandatory programs that have a specific purpose in the context of integrating each challenge into the CTF infrastructure. These programs are described below.

ProgramPurpose
buildThis executable builds the challenge from the sources and the configuration (flag)
deployThis executable perform the deployment-related operations for the challenge
healthcheckThis executable performs a healcheck on the deployed challenge (checking checksum or playing an exploit)

Previously defined programs shall respect the following specifications:

IDSpecificationComments
S0The executable shall not take positional argumentsThe caller will not provide any positional argument
S1The executable shall be able to to handle an optional --dev argumentThis argument might be given by the caller which will expect the executable to run in development mode when applicable
S2The executable execution shall end before a timeout is triggeredTimeout defaults to 2 minutes. --timeout option enable you to override this value
S3The executable shall return an exit codeThis code will be interpreted using the rules defined in the next table

As explained in S3 the program exit code will be interpreted according to the following table:

Exit CodeMeaningDescription
0SUCCESSThe executable execution succeeded
2N/AThe executable does have a meaning in the context of this challenge
3MANUALThe executable cannot perform this task entirely, you will have to get your hands dirty
4NOT IMPLEMENTEDThe executable is not implemented yet
OTHERFAILUREThe executable execution failed

When using build, deploy or healthcheck commands, the CLI will behave as described below.

If the exit code differs from 0 executable output (both stdout and stderr) will be printed out. You can use this behavior to print meaningful instructions from within these programs. This behavior is particularly interesting if your program returns a code 3 which means the user must perform a manual operation to complete the task.

A special status TIMEOUT may occur when using build, deploy or healthcheck commands. In that case, it means that your executable took too long to execute as explained in specificationS2.

Dashboard

The dashboard is expected to provide HTTP APIs defined below. Generic endpoint specifications are the following:

IDSpecificationDetails
R1The dashboard API shall implement HTTPS with a valid certificatemkctf-cli will always use https scheme to post the configuration
R2The dashboard API endpoint shall implement basic authenticationmkctf-cli will set the Authorization header using Basic method
R3The dashboard API endpoint shall expect a HTTP POST querymkctf-cli will POST challenge configuration to the dashboard
R4The dashboard API endpoint shall expect a application/json bodymkctf-cli will POST a JSON body to the dashboard

Configuration Synchronization API

mkctf-cli push command allows to push every challenge configuration to the dashboard.

POST /mkctf-api/push
{
  "challenges": [
    {
      "name": "Dont Mess With My Memory",
      "slug": "dont-mess-with-my-memory",
      "tags": [
        "pwn"
      ],
      "flag": "ECTF{fake_flag_for_u_buddy}",
      "author": "koromodako",
      "points": -3,
      "enabled": true,
      "category": "sandbox",
      "logo_url": "",
      "difficulty": "easy",
      "static_url": "https://static.example.ctf/4f9bb3ac75a3cc0e5995aaa1af345ab62de6a697.tar.gz"
    }
  ]
}

The dashboard shall store flags using a secure hashing mecanism such as argon2.

Healthcheck Notification API

mkctf-monitor will push reports to the dashboard.

POST /mkctf-api/healthcheck
{
    "dont-mess-with-my-memory": true,
    "stylish-leak": false,
}

Previous body means dont-mess-with-my-memory is healthy and stylish-leak isn't.