LabStore
← All labs

<Lab Name>

by selfmade ✓ first-party

<Description>

machine lab image ready

Development

CTF Development Template

Description

This is a framework developed by Selfmade Ninja Labs to make CTF development process easy. The template provides a comprehensive structure for creating, managing, and deploying Capture The Flag (CTF) challenges with standardized configurations, automated deployment pipelines, and integrated scoring systems.

Overview

This template streamlines the CTF challenge creation workflow by providing:

  • Standardized project structure
  • Automated Docker containerization
  • CI/CD pipeline integration
  • Built-in scoring and achievement systems
  • Challenge management utilities
  • Resource allocation controls

Documentation sections below will be expanded as we go through each file and component...

Project Structure

ctf-template/
├── config.json          # Main lab configuration
├── challenges.json      # Challenge definitions and metadata
├── achievements.json    # Achievement and scoring rules
├── .gitlab-ci.yml      # CI/CD pipeline configuration
├── briefs/             # Challenge brief descriptions
├── hints/              # Hint files organized by challenge
├── solutions/          # Solution writeups and answers
├── data/               # Challenge assets and resources
├── plans/              # Lab deployment plans
├── mission_assets/     # Visual assets for challenges
│   └── projectname/    # Project-specific assets (avatars, covers)
└── README.md           # This documentation file

Getting Started

(This section will be expanded with detailed setup instructions)

Configuration Files

config.json - Main Lab Configuration

The config.json file contains the core configuration for your CTF lab. This file defines how your challenge environment will be deployed, managed, and accessed.

Mandatory Fields to Update
Field Description Example
lab_name Must be same as git project name, lowercase with hyphens "web-exploitation-lab"
lab_id Same as lab_name "web-exploitation-lab"
hostname Format: {git-project-id}.selfmade.ninja "web-exploit.selfmade.ninja"
Basic Information Fields
Field Description Example
author Challenge author name "John Doe"
author_email Author's email address "john@example.com"
story_name Story/theme name for the challenge "Secret Mission"
story_description Brief story description "Infiltrate the enemy server"
story_id Unique story identifier "secret-mission-001"
story_index Story sequence number "1"
description Lab description "Web exploitation challenges"
type Challenge type/category "web"
Visual Assets
Field Description
avatar Cover image for the main challenge display
avatar_small Avatar image (smaller thumbnail)
Additional Optional Fields
Field Description Example
event Event name (must be pre-created) "yukthi-finals-2024"
mode Challenge mode "team"
Environment Configuration
Field Description Options/Example
version Lab version 0.1
stability Lab stability level "beta" (public) or "alpha" (admin only)
shell Default shell "/bin/bash"
ports Open ports for Docker image "80" (default if unsure)
cpu CPU allocation 1
mem Memory allocation "256m"
interface Network interface "eth0"
Time Management System
Field Description Example
expireable Enable time-limited challenge true
expiry Challenge duration "50 mins"
renewable Allow time renewal true
renewal_max Maximum renewals allowed 20
renewal_period Time added per renewal "2 mins"
renewal_jolt Jolt points required for renewal 2
System Commands

The cmds section defines system scripts for lab lifecycle management:

"cmds": {
    "linkuser": "/var/labsdata/scripts/linkuser.sh",
    "entry": "/var/labsdata/scripts/entry.sh", 
    "spawn": "/var/labsdata/scripts/spawn.sh",
    "unregister": "/var/labsdata/scripts/unregister.sh"
}
Connection Information

The connection_info array defines how users access the lab environment:

  • Device IP: Dynamic IP address placeholder
  • Device Port: Dynamic port placeholder
  • VS Code Forward: For development environments
  • Local Forward: Using socat for local port forwarding
Example Configuration
{
    "author": "Jane Smith",
    "author_email": "jane@selfmade.ninja",
    "lab_name": "web-security-basics",
    "lab_id": "web-security-basics", 
    "hostname": "web-security-basics.selfmade.ninja",
    "story_name": "Corporate Espionage",
    "story_description": "Infiltrate the corporate network",
    "version": 0.1,
    "stability": "beta",
    "ports": "80",
    "expireable": true,
    "expiry": "45 mins",
    "renewable": true,
    "renewal_max": 15,
    "renewal_period": "3 mins", 
    "renewal_jolt": 3,
    "event": "yukthi-finals-2024",
    "mode": "team"
}

challenges.json - Challenge Definitions

The challenges.json file contains configuration for each individual challenge in your CTF lab. This file defines all the challenges, their properties, scoring, and relationships.

Structure

The file contains an array of challenge objects, where each challenge has the following properties:

Basic Challenge Information
Field Description Example
id Unique challenge identifier (integer) 1
name Challenge name/title "Friend's Hidden Identity"
description Short description about the challenge "Decode the accomplice's real identity"
category Array of challenge categories ["web", "osint"]
Challenge Content
Field Description Example
brief Reference to brief file in briefs/ directory "0.md"
solution Reference to solution file in solutions/ directory "0.md"
Visual Assets
Field Description Example
avatar Challenge avatar image path "projectname/2nd-avatar.png"
cover Challenge cover image path "projectname/2nd-back.jpg"

Note: Images are stored in mission_assets/ directory

Scoring Configuration
Field Description Example
difficulty_percent Difficulty percentage (0-100)<br/>• Below 20: Easy<br/>• 20-50: Medium<br/>• Above 50: Hard 75
value Base point value for the challenge 200
bonus Additional bonus points 0
value_multiplier Multiplier for final score calculation 1.5
Challenge State
Field Description Example
locked Whether challenge is initially locked false
requires Array of challenge IDs that must be completed first [1]
Hints System

The hints array defines available hints for the challenge:

"hints": [{
    "cost": 25,
    "hint": "0.md"
}, {
    "cost": 40, 
    "hint": "1.md"
}]
Field Description
cost Jolt points required to unlock hint
hint Reference to hint file in hints/{challenge_id}/ directory
Example Challenge Configuration
{
    "id": 2,
    "name": "Friend's Hidden Identity",
    "description": "Decode the accomplice's real identity and contact details.",
    "category": ["web", "osint"],
    "brief": "1.md",
    "hints": [{
        "cost": 25,
        "hint": "0.md"
    }, {
        "cost": 40,
        "hint": "1.md"
    }],
    "avatar": "projectname/2nd-avatar.png",
    "cover": "projectname/2nd-back.jpg",
    "difficulty_percent": 75,
    "value": 200,
    "bonus": 0,
    "locked": true,
    "value_multiplier": 1.5,
    "solution": "0.md",
    "requires": [1]
}
Challenge Dependencies

Use the requires field to create challenge sequences:

  • Empty array [] = No dependencies (can be attempted immediately)
  • [1] = Must complete challenge ID 1 first
  • [1, 2] = Must complete both challenges 1 and 2 first

achievements.json - Achievement and Scoring System

The achievements.json file defines special achievements that players can unlock based on their performance and completion criteria.

Achievement Generation Tool

For automated achievement generation, use the Labs Challenge Automation repository:

python achievements.py <num_challenges> <challenge_folder> --names <challenge_names>... --difficulty <difficulty>

Parameters:

  • <num_challenges>: Number of challenges in your lab
  • <challenge_folder>: Path to your challenge directory
  • --names: List of challenge names
  • --difficulty: Overall difficulty level
Achievement Structure

Each achievement object contains the following properties:

Field Description Example
id Unique achievement identifier "complete_challenge_all_in_20min"
name Achievement name/title "Complete all challenges within 20 minutes"
avatar Achievement badge image path "cmd/badge-8.png"
Rewards System

Achievements can grant various rewards:

"rewards": {
    "jolt": 25,
    "zeal": 70
}
Reward Type Description
jolt Jolt points awarded
zeal Zeal points awarded
Requirements Configuration

Define achievement unlock criteria:

"requirements": {
    "challenges": "all",
    "complete_within": "20 mins"
}
Requirement Description Options
challenges Which challenges must be completed "all", specific challenge IDs
complete_within Time limit for completion "20 mins", "1 hour", etc.
Example Achievement
{
    "id": "complete_challenge_all_in_20min",
    "name": "Complete all challenges within 20 minutes",
    "avatar": "cmd/badge-8.png",
    "rewards": {
        "jolt": 25,
        "zeal": 70
    },
    "requirements": {
        "challenges": "all",
        "complete_within": "20 mins"
    }
}

Challenge Development

Dockerfile Requirements

When developing your CTF challenge, you must create a Dockerfile to containerize your environment.

Mandatory Lines (DO NOT MODIFY)

The following lines are required and must be included in your Dockerfile exactly as shown:

COPY ./data /var/labsdata
RUN chmod 700 /var/labsdata
RUN chmod +x /var/labsdata/scripts/*.sh
CMD "/var/labsdata/scripts/entry.sh"

⚠️ Warning: These lines are essential for the lab management system to function properly. Modifying or removing them will cause deployment failures.

Dockerfile Structure

Your Dockerfile should follow this general structure:

# Base image (choose appropriate base)
FROM ubuntu:20.04

# Install your application dependencies
RUN apt-get update && apt-get install -y \
    package1 \
    package2 \
    && rm -rf /var/lib/apt/lists/*

# Copy your application files
COPY . /app
WORKDIR /app

# Configure your application
RUN your-setup-commands

# Expose required ports (must match config.json)
EXPOSE 80

# MANDATORY LINES - DO NOT MODIFY
COPY ./data /var/labsdata
RUN chmod 700 /var/labsdata
RUN chmod +x /var/labsdata/scripts/*.sh
CMD "/var/labsdata/scripts/entry.sh"
Important Notes
  1. Port Configuration: Ensure the EXPOSE directive matches the ports field in your config.json
  2. Base Image: Choose an appropriate base image for your challenge type
  3. Dependencies: Install all required packages and dependencies
  4. File Permissions: Set appropriate permissions for your application files
  5. Working Directory: Set up your application's working directory before the mandatory lines
Example Dockerfile Templates

Web Application Example:

FROM nginx:alpine
COPY ./web-content /usr/share/nginx/html
EXPOSE 80

# MANDATORY LINES
COPY ./data /var/labsdata
RUN chmod 700 /var/labsdata
RUN chmod +x /var/labsdata/scripts/*.sh
CMD "/var/labsdata/scripts/entry.sh"

Python Application Example:

FROM python:3.9-slim
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY ./app /app
WORKDIR /app
EXPOSE 8080

# MANDATORY LINES
COPY ./data /var/labsdata
RUN chmod 700 /var/labsdata
RUN chmod +x /var/labsdata/scripts/*.sh
CMD "/var/labsdata/scripts/entry.sh"

data/scripts/entry.sh - Container Startup Script

The entry.sh file is responsible for the startup of the Docker container. This script is executed when the container starts and can be used to initialize services.

Default Content (Required)

The file must contain these lines at minimum:

#!/bin/sh

tail -f /dev/null

⚠️ Important: The tail -f /dev/null command is mandatory and must be the last line to keep the container running.

Usage Guidelines

You can modify this file to start initial services, but follow these rules:

✅ Allowed:

  • Start SSH services
  • Initialize databases
  • Set up system configurations
  • Start background services

❌ Prohibited:

  • DO NOT start web servers (Apache, Nginx, etc.)
  • DO NOT start application servers that expose the challenge directly

Why no web servers? Starting web servers immediately may allow CTF players to access the website before the challenge is properly deployed and configured.

Example Configurations

Basic Setup (Default):

#!/bin/sh

tail -f /dev/null

With SSH Service:

#!/bin/sh

# Start SSH service
service ssh start

# Keep container running
tail -f /dev/null

With Database Setup:

#!/bin/sh

# Initialize database
mysql -u root -e "CREATE DATABASE ctf_db;"

# Start required services
service mysql start

# Keep container running
tail -f /dev/null

With System Configuration:

#!/bin/sh

# Set up users
useradd -m ctfuser

# Configure permissions
chmod 755 /app

# Keep container running
tail -f /dev/null
File Location

This file must be located at: data/scripts/entry.sh

Permissions

The file permissions are automatically set by the mandatory Dockerfile line:

RUN chmod +x /var/labsdata/scripts/*.sh

data/scripts/spawn.sh - Challenge-Specific Deployment Script

The spawn.sh script is the most powerful script in the CTF template. It executes when a user clicks "Start Mission" and allows challenge-specific configuration and dynamic flag generation.

How It Works

The script receives two parameters:

  • $1 - Challenge ID (from challenges.json)
  • $2 - Dynamically generated flag for the user
Script Structure
challid=$1
flag=$2

case $challid in
    "1")
        <Your Spawn Scripts or commands goes here...>
        echo "Challenge $challid spawned with $flag"
        ;; 
    "2")
        <Your Spawn Scripts or commands goes here...>
        echo "Challenge $challid spawned with $flag"
        ;; 
esac
Key Features

🚀 Dynamic Flag Injection

  • Each user gets a unique, randomly generated flag
  • Flags are generated on-the-fly when mission starts
  • Perfect for preventing flag sharing between users

🎯 Challenge-Specific Logic

  • Different setup for each challenge ID
  • Start services only for specific challenges
  • Customize environment per challenge

⚡ Real-Time Deployment

  • Executes when user clicks "Start Mission"
  • Allows last-minute configuration
  • Can modify files, start services, set permissions
Common Use Cases

1. Dynamic Flag Placement:

challid=$1
flag=$2

case $challid in
    "1")
        # Replace placeholder with actual flag
        sed -i "s/FLAG_PLACEHOLDER/$flag/g" /var/www/html/config.php
        echo "Web challenge spawned with flag: $flag"
        ;; 
    "2")
        # Write flag to hidden file
        echo "$flag" > /home/user/.secret_flag
        chmod 600 /home/user/.secret_flag
        echo "OSINT challenge spawned with flag: $flag"
        ;; 
esac

2. Challenge-Specific Services:

challid=$1
flag=$2

case $challid in
    "1")
        # Only start web server for challenge 1
        service apache2 start
        echo "$flag" > /var/www/html/flag.txt
        echo "Web server started for challenge 1"
        ;; 
    "2")
        # Only start SSH for challenge 2
        service ssh start
        echo "$flag" > /root/flag.txt
        echo "SSH service started for challenge 2"
        ;; 
    "3")
        # Start database for challenge 3
        service mysql start
        mysql -u root -e "INSERT INTO flags (value) VALUES ('$flag');"
        echo "Database challenge spawned"
        ;;
esac

3. File System Modifications:

challid=$1
flag=$2

case $challid in
    "1")
        # Create challenge-specific directory structure
        mkdir -p /challenge1/hidden
        echo "$flag" > /challenge1/hidden/secret.txt
        
        # Set specific permissions
        chmod 755 /challenge1
        chmod 700 /challenge1/hidden
        
        echo "File system challenge prepared"
        ;; 
    "2")
        # Modify configuration files
        sed -i "s/SECRET_KEY/$flag/g" /app/config.ini
        
        # Start application
        cd /app && python app.py &
        
        echo "Application challenge started"
        ;;
esac

4. User Account Setup:

challid=$1
flag=$2

case $challid in
    "1")
        # Create user with flag as password
        useradd -m ctfuser
        echo "ctfuser:$flag" | chpasswd
        
        service ssh start
        echo "SSH brute force challenge ready"
        ;; 
    "2")
        # Hide flag in user's history
        echo "cat /root/secret.txt # $flag" >> /home/user/.bash_history
        
        echo "Forensics challenge prepared"
        ;;
esac
Best Practices

✅ Do:

  • Use the provided $flag variable for dynamic flags
  • Include echo statements for logging
  • Handle different challenge IDs appropriately
  • Start web servers here (not in entry.sh)
  • Test each case thoroughly

❌ Don't:

  • Hardcode flags in the script
  • Forget to handle all challenge IDs from challenges.json
  • Start conflicting services
  • Ignore error handling
File Location

This file must be located at: data/scripts/spawn.sh

Integration with Other Components
  • challenges.json: Challenge IDs must match case statements
  • entry.sh: Keep entry.sh minimal, use spawn.sh for challenge logic
  • Dockerfile: Permissions set automatically via mandatory lines

Other Script Files

The following script files are provided in the template and should NOT be modified:

Script Purpose Action Required
data/scripts/linkuser.sh User linking functionality ❌ Do not modify
data/scripts/unregister.sh User cleanup and unregistration ❌ Do not modify

These scripts are part of the core lab management system and are automatically configured by the framework.

data/ Directory - Challenge Files Storage

The data/ directory is your main workspace for all challenge-related files and assets.

Key Features

📁 Complete Container Copy

  • The entire data/ folder is copied into the container via the mandatory Docker command:
    COPY ./data /var/labsdata
    

🎯 Store Everything Here

  • Web application files
  • Configuration files
  • Source code
  • Assets and resources
  • Any files needed for your challenge
Directory Structure Examples
data/
├── scripts/           # System scripts (entry.sh, spawn.sh, etc.)
├── www/              # Web application files
│   ├── index.html
│   ├── css/
│   └── js/
├── configs/          # Configuration files
│   ├── nginx.conf
│   └── app.config
├── src/              # Source code
│   ├── vulnerable_app.py
│   └── database.sql
└── assets/           # Challenge assets
    ├── images/
    └── documents/
Best Practices

✅ Organize by Purpose:

  • Create subdirectories for different file types
  • Keep scripts in scripts/ directory
  • Group related files together

✅ File Accessibility:

  • All files in data/ become available in the container at /var/labsdata/
  • Reference files using absolute paths: /var/labsdata/www/index.html
  • Set appropriate permissions in your Dockerfile or spawn.sh

Example Usage in spawn.sh:

case $challid in
    "1")
        # Copy web files to Apache directory
        cp -r /var/labsdata/www/* /var/www/html/
        
        # Use configuration file
        cp /var/labsdata/configs/nginx.conf /etc/nginx/nginx.conf
        
        # Replace flag in source code
        sed -i "s/FLAG_PLACEHOLDER/$flag/g" /var/labsdata/src/app.py
        ;;
esac

plans/ Directory - Configuration Override

The plans/ directory allows you to override settings from config.json for different deployment scenarios.

Default Behavior

By default, all challenges are free and accessible to all users.

Premium Challenge Configuration

To make your challenge premium (paid access), create configuration override files in the plans/ directory.

Example: plans/premium.json

{
    "free": false
}
Override Options

You can override any config.json setting by creating corresponding files in plans/:

plans/free.json (Default)

{
    "free": true
}

plans/free.json (Paid Access)

{
    "free": false
}

plans/admin.json (Custom Settings)

{
    "free": false,
    "expiry": "2 hours",
    "renewal_max": 5,
    "cpu": 2,
    "mem": "512m"
}
How It Works
  1. Base Configuration: config.json contains default settings
  2. Plan Override: Files in plans/ override specific settings
  3. Deployment: The system applies the appropriate plan during deployment
  4. Access Control: "free": false restricts access to premium users only
Use Cases

Free Challenge (Default):

  • No plans/ files needed
  • Or create plans/free.json with "free": true

Premium Challenge:

  • Create plans/premium.json with "free": false
  • Challenge requires paid access

Custom Resource Allocation:

  • Override CPU, memory, or time limits
  • Create specialized deployment configurations
  • Different settings for different user tiers
Best Practices

✅ Plan Organization:

  • Use descriptive filenames (free.json, premium.json, enterprise.json)
  • Keep override files minimal (only changed settings)
  • Document which plan is intended for which use case

✅ Testing:

  • Test both free and premium configurations
  • Verify access controls work correctly
  • Ensure resource allocations are appropriate

Development Commands

The following commands are used for building and testing your CTF challenges. These commands require labsctl to be installed, which is available by default on intern/employee server access.

Local Development Build

For local development and testing:

labsctl build -f <lab_id>:lab --no-cache

Usage:

  • Replace <lab_id> with your lab ID from config.json
  • Use this command when developing locally
  • The --no-cache flag ensures fresh builds
  • This builds the image from your local files

Example:

labsctl build -f web-security-basics:lab --no-cache
Install from Git Repository

For installing directly from your Git repository:

labsctl install -f <lab_id>:lab --no-cache

Usage:

  • Use this after pushing your code to Git
  • Installs and deploys the lab from the repository
  • Automatically pulls latest changes from Git
  • The lab will be available in the UI after successful installation

Example:

labsctl install -f web-security-basics:lab --no-cache
Deployment Logs

Monitor deployment progress and troubleshoot issues:

# View deployment logs
tail -f /var/log/DeployLog

# View specific deployment log
cat /var/log/DeployLog

The deployment logs contain detailed information about:

  • Build process status
  • Error messages and debugging info
  • Container deployment progress
  • Service startup confirmations
Development Workflow
  1. Develop Locally: Create your challenge files and configurations
  2. Local Build: Test with labsctl build command
  3. Push to Git: Commit and push your changes
  4. Deploy: Use labsctl install to deploy from Git
  5. Monitor: Check /var/log/DeployLog for deployment status
  6. Test: Access your lab through the UI
Prerequisites
  • Server Access: Available for interns and employees
  • labsctl: Pre-installed on provided servers
  • Git Repository: Your challenge code must be in a Git repository
  • Proper Configuration: All configuration files must be properly set up

.gitlab-ci.yml - CI/CD Pipeline

The .gitlab-ci.yml file contains the automated CI/CD pipeline configuration for your CTF lab.

⚠️ Important: DO NOT modify this file. It is pre-configured for the Selfmade Ninja Labs infrastructure and any modifications may break the automated deployment process.

Testing Your Challenge

Use the local build command to test your challenge before deployment:

labsctl build -f <lab_id>:lab --no-cache

This command will:

  • Build your Docker container locally
  • Validate your configuration files
  • Test the challenge deployment process
  • Show any errors or issues before actual deployment

Deployment

The deployment process is handled automatically through the labsctl commands and CI/CD pipeline integration.