Skip to main content
Challenges are the core of any CTF competition. GZCTF supports multiple challenge types with sophisticated flag validation and container orchestration.

Challenge Types

GZCTF supports five distinct challenge types:

Static Attachment

Single attachment file with fixed flags. Perfect for reverse engineering, forensics, or cryptography challenges.

Dynamic Attachment

Unique attachment per team generated from templates. Prevents flag sharing between teams.

Static Container

Shared Docker container with fixed flags. Suitable for web exploitation or network services.

Dynamic Container

Individual container per team with unique flags. Maximum isolation and fairness for pwn/web challenges.

Static Content

No files or containers—challenge info is in the description. Used for OSINT or trivia.

Creating Challenges

1

Navigate to Game Challenges

Open the game in admin panel, go to Challenges tab, and click Add Challenge.
2

Set Basic Properties

Title
string
required
Challenge name displayed to participants
Category
enum
required
Category classification:
  • Web - Web exploitation
  • Pwn - Binary exploitation
  • Reverse - Reverse engineering
  • Crypto - Cryptography
  • Forensics - Digital forensics
  • Misc - Miscellaneous
  • Hardware - Hardware hacking
  • Mobile - Mobile security
  • PPC - Programming challenges
  • Blockchain - Web3/blockchain
  • AI - Artificial intelligence
Type
enum
required
Challenge type (see above section)
3

Configure Challenge Content

Content
string
Markdown-formatted challenge description, hints, and connection information

Scoring Configuration

Dynamic Scoring System

GZCTF implements dynamic scoring where challenge points decrease as more teams solve them:
OriginalScore
integer
default:500
Initial point value before any solves
MinScoreRate
double
Minimum score as a fraction of original (e.g., 0.4 = 40% minimum)
Difficulty
double
Controls how quickly score decreases. Higher values = slower decay.
def calculate_score(original_score, min_score_rate, difficulty, solved_count):
    """
    Calculate dynamic challenge score based on solve count
    """
    min_score = original_score * min_score_rate
    current_score = (
        (original_score - min_score) * 
        (difficulty / (solved_count + difficulty)) +
        min_score
    )
    return max(int(current_score), min_score)

# Example: 500pt challenge, 40% min, difficulty 3
print(calculate_score(500, 0.4, 3.0, 0))   # 500 (no solves)
print(calculate_score(500, 0.4, 3.0, 3))   # 350 (3 solves)
print(calculate_score(500, 0.4, 3.0, 10))  # 238 (10 solves)
print(calculate_score(500, 0.4, 3.0, 50))  # 200 (minimum)
Use higher difficulty values (5-10) for easy challenges to keep them valuable longer. Use lower values (1-2) for hard challenges.

Blood Bonus

DisableBloodBonus
boolean
default:false
Disable blood bonus for this specific challenge (bonus still applies to other challenges)

Flag Management

Flags are the answers teams submit to earn points.

Static Flags

For static challenge types, add one or more flags manually:
1

Add Flag

Click Add Flag in the challenge edit page
2

Configure Flag

Flag
string
required
The flag value (e.g., flag{this_is_a_flag})
Type
enum
default:"Text"
  • Text - Exact string match (case-insensitive)
  • RegExp - Regular expression pattern
# Accept multiple formats
flag\{[a-f0-9]{32}\}

# Flexible spacing
flag\{\s*welcome[_\s]*to[_\s]*gzctf\s*\}

Dynamic Flags

For dynamic container/attachment challenges, use flag templates:
FlagTemplate
string
required
Template with placeholders for unique flag generation:
  • [TEAM_HASH] - Unique team identifier
  • [GAME_ID] - Game identifier
  • [CHALLENGE_ID] - Challenge identifier
# Basic team-unique flag
flag{[TEAM_HASH]}

# Complex format
flag{team_[TEAM_HASH]_game_[GAME_ID]_challenge_[CHALLENGE_ID]}

# Realistic-looking flags
GZCTF{[TEAM_HASH]_c0ngr4tul4t10ns}
Flag templates must not be too simple. The system validates that [TEAM_HASH] is included to prevent static flags disguised as dynamic ones.

Container Configuration

For container-based challenges (Static/Dynamic Container):

Container Image

ContainerImage
string
required
Docker image name with tag (e.g., gzctf/web-challenge:latest)
1

Prepare Image

Build your challenge image and push to a container registry (Docker Hub, GHCR, private registry)
2

Configure Exposed Port

ExposePort
integer
required
Port your service listens on inside the container

Resource Limits

Prevent resource exhaustion:
MemoryLimit
integer
default:64
Memory limit in MB (range: 32-1048576)
CPUCount
integer
default:1
CPU limit in 0.1 CPU units (e.g., 10 = 1.0 CPU, 5 = 0.5 CPU)
StorageLimit
integer
default:256
Disk storage limit in MB (range: 0-1048576)
ContainerImage: myregistry/web-chall:v1
MemoryLimit: 128        # 128 MB RAM
CPUCount: 5             # 0.5 CPU cores  
StorageLimit: 512       # 512 MB disk
ExposePort: 80

Network Mode

NetworkMode
enum
default:"Open"
  • Open - Full internet access
  • Isolated - No external network access
  • DNS - DNS resolution only
Use Isolated for challenges that don’t need internet to prevent container escape or exfiltration attacks.

Traffic Capture

EnableTrafficCapture
boolean
default:false
Record all network traffic for forensics/monitoring. Only available for container challenges.
Captured traffic is stored as PCAP files accessible to admins for cheat detection or debugging.

Attachment Management

Static Attachments

For Static Attachment/Container challenges:
1

Upload File

Click Update Attachment and select your file
2

Configure Display Name

AttachmentType
enum
  • Remote - File on external server (provide URL)
  • Local - File stored in GZCTF

Dynamic Attachments

For Dynamic Attachment challenges:
FileName
string
required
Base filename for generated attachments (e.g., challenge.zip)
1

Prepare Template Package

Create a ZIP archive containing:
  • Challenge files
  • Template files with [TEAM_HASH] placeholders
2

Upload Template

System generates unique attachments per team by replacing placeholders

Advanced Features

Submission Limits

SubmissionLimit
integer
default:0
Maximum flag submissions per team (0 = unlimited). Useful for preventing brute-force attacks.
SubmissionLimit: 5      # Only 5 attempts allowed

Challenge Deadline

DeadlineUtc
datetime
Optional deadline before game end. Challenge becomes inaccessible after this time.

Hints System

Hints
array[string]
Progressive hints revealed at scheduled times or costs
1

Add Hints

Add multiple hint strings in the challenge editor
2

Automatic Notification

When hints are updated during an active game, a system notice is automatically published

Testing Challenges

Container Test Mode

Before enabling a challenge, test containers:
1

Create Test Container

Click Test Container button in challenge editor
2

Verify Functionality

  • Check service responds on exposed port
  • Verify dynamic flags are correctly generated
  • Test flag submission
3

Destroy Test Instance

Click Destroy Test Container when testing is complete
Test containers use admin resources and count toward your container quotas. Remember to destroy them after testing.

Enabling Challenges

IsEnabled
boolean
default:false
Make challenge visible and solvable by participants
1

Validate Configuration

Before enabling, system checks:
  • At least one flag exists (for non-dynamic challenges)
  • Container image is configured (for container challenges)
  • Flag template is valid (for dynamic challenges)
2

Enable Challenge

Toggle IsEnabled to true
3

Container Initialization

For container challenges, system pre-creates instances for faster deployment
When enabled during an active game, a system notice “New challenge available: [Title]” is automatically broadcast.

Bulk Operations

Export/Import Games

Export entire games including all challenges:
curl -X POST https://your-gzctf.com/api/edit/games/1/export \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -o game-backup.zip

Delete Challenge

Deleting a challenge also removes:
  • All flags
  • Submission history
  • Container instances
  • Team progress for that challenge
This action is irreversible.

API Reference

Create new challenge. Returns challenge ID and details.
Get detailed challenge configuration including flags (except dynamic container flags).
Update challenge settings. Flags are managed separately via flag endpoints.
Permanently delete challenge and all related data.
Add one or more flags to the challenge.
Remove a specific flag.
Upload or update challenge attachment.
Create test container instance.
Destroy test container instance.

Best Practices

Test Thoroughly

Always test challenges before enabling:
  • Verify flags work
  • Check container stability
  • Test resource limits

Set Appropriate Resources

Balance between:
  • Challenge needs
  • Infrastructure capacity
  • Participant experience

Use Dynamic Flags

For serious competitions, use dynamic flags to prevent:
  • Flag sharing
  • Early leaks
  • Unfair advantages

Clear Descriptions

Include in challenge content:
  • Connection information
  • Expected flag format
  • Difficulty hints
  • Author credits

Troubleshooting

Check error message:
  • “No flags”: Add at least one flag
  • “Container config error”: Verify image and port settings
  • “Flag template invalid”: Ensure [TEAM_HASH] is included
Verify:
  • Image exists in registry
  • Registry credentials configured (if private)
  • Resource limits are reasonable
  • Container provider is running
Confirm:
  • Flag template includes [TEAM_HASH]
  • Challenge type is Dynamic Container/Attachment
  • Container/attachment generates flag correctly
Check:
  • File was uploaded successfully
  • Storage backend is accessible
  • File permissions are correct

Next Steps

Team Management

Manage participant registrations

Monitoring

Monitor competition progress

Build docs developers (and LLMs) love