This is the multi-page printable view of this section. Click here to print.
CTFd-chall-manager
- 1: Get started
- 1.1: Setup
- 1.2: Create a challenge
- 1.3: Play the challenge
- 2: Guides
- 3: Design
1 - Get started
1.1 - Setup
Goal
This tutorial will guide you through the installation and configuration of the CTFd-chall-manager plugin to use chall-manager.
Prerequisites
Ensure you have chall-manager running before starting this tutorial. You can find the relevant documentation for setup instructions.
Install the plugin
If you are not using the docker-compose.yml
file, you need to clone the repository into CTFd/CTFd/plugins/ctfd-chall-manager
.
# Clone the CTFd repository
git clone https://github.com/CTFd/CTFd
# Clone the plugin repository
git clone https://github.com/ctfer-io/ctfd-chall-manager CTFd/CTFd/plugins/ctfd-chall-manager
# Start Redis with Docker
docker run -d -p 6379:6379 redis:<version>
## Configure plugin to use redis serveur
export REDIS_URL=redis://localhost:6379
# Start CTFd
cd CTFd
python3 -m venv venv
source venv/bin/activate
pip3 install -r requirements.txt
python3 serve.py
# Clone the plugin repository
git clone https://github.com/ctfer-io/ctfd-chall-manager
# Create Docker network
docker network create testing
# Start Redis with Docker
docker run -d --name redis-svc --network testing redis:<version>
# Start CTFd with Docker
docker run -d -p 8000:8000 -e REDIS_URL=redis://redis-svc:6379 -v ./ctfd-chall-manager:/opt/CTFd/CTFd/plugins/ctfd-chall-manager --network testing ctfd/ctfd:<version>
After completing this step, you should be able to access the plugin settings configuration in the CTFd UI.
If the plugin does not appear, verify your container volume mounts, then check the CTFd logs for import module entries, such as:
Configure the plugin to use chall-manager
To connect the plugin to chall-manager, go to the plugin settings.
The default configuration is:
Adjust the plugin settings to match your environment, ensuring CTFd can communicate with chall-manager. For instance:
What’s next?
Congratulations! At this point, your setup is ready to use chall-manager for your CTF events.
1.2 - Create a challenge
Goal
In this tutorial, we will create a dynamic_iac
challenge, a new challenge type introduced by the plugin.
If you are unfamiliar with the new attributes of the dynamic_iac
challenge type, please refer to the related design.
For guidance on maintenance operations (e.g., modifying challenge attributes), please refer to the relevant guides.
For details on the Infra-as-Code scenario, consult the appropriate documentation.
Create the Challenge
For this example, we will create a challenge where each player gets their own instance. The instance will cost user 2 mana units, must be destroyed after 10 minutes without maximum due date, and using the scenario from the no-sdk examples.
Here are the basic CTFd settings:
Key | Value |
---|---|
Name | example |
Category | example |
Message | example |
Initial Value | 500 |
Decay Function | Logarithmic |
Decay | 10 |
Minimum Value | 10 |
First, configure the scope. Since we want each player to have their own instance, disable the global scope.
Next, set the mana cost. Players will need to spend 2 mana to deploy their own instance of the challenge.
As mentioned, we want instances to be destroyed after 10 minutes of usage (600 seconds), without any due date.
Let the Until value empty, and configure de Timeout value at 600.
Then, provide the scenario archive. For this example, we’ll use demo-deploy.zip
from the no-sdk examples.
Finally, click Create to set up the challenge.
Note
When you click Create, the upload process to the chall-manager may take several seconds. Please be patient.What’s Next?
Congratulations! Your CTF installation is now configured to use the chall-manager for this challenge.
1.3 - Play the challenge
Goal
In this tutorial we will see all actions a user has access to in order to control its instances.
Prerequisites
At this step, we assume that you are a CTF player, the infrastructure is already configured and you understand the key concepts, if not please refer to associated design.
Differents challenges mode
You can combine the values Until and Timeout to have 4 modes according to your needs:
Players can control all combinaisons, but each has these specificities.
Note
If global scope is enabled, only admins can launch the instance, see associated guide.For all challenges, the default view (instance is not booted) will display a button to launch the instance, the mana cost and the remaining mana for current user.
To start the instance, click on the button.
None
This mode allows you to use your instance with no duration limit. Its deployment can cost you mana, but if you want to regain it, you need to destroy the instance. If you want to reset your instance (either because it is soft-locked or you corrupted it), you can restart it.
Until
This mode allows you to use the same actions as None mode, but the instance will be destroyed by the Janitor at a due date. Don’t worry, your mana will be automatically regained if the instance is janitored.
Timeout
This mode allows you to use the same actions as None and Until, but the instance will be destroyed by the Janitor n seconds after their start.
If you see that the Janitor will destroy you instance soon, you can renew the instance (reset the timer).
Both
With Timeout and Until, the plugin will display the timeout mode buttons, but Chall-Manager will take care of restricting or not the possibility of renewing the challenge based on the design.
What’s next ?
Congrat’s ! If you’ve made it this far, you’ll probably want to make a challenge for your event, and the plugin documentation doesn’t include explanations on how to do this, so please refer to the associated documentation.
Otherwise, you can learn more about the plugin’s design or advanced user guides here:
2 - Guides
2.1 - Settings
Goal
This guide assumes you are a CTF administrator and you understand the key concepts. Before or during your event, you may need to configure or update the plugin. At the moment, you can configure the total amount of mana for Source or and the chall-manager API URL.
Configure with environment variables
To configure the plugin at CTFd startup, you can use the next environment variables:
Variable | Default | Description |
---|---|---|
PLUGIN_SETTINGS_CM_API_URL | http://localhost:9090/api/v1 | URL of Chall-Manager API |
PLUGIN_SETTINGS_CM_MANA_TOTAL | 0 | Maximum mana that source are allowed to use |
Note
The environment variable lookup is triggered at CTFd first startup. To modify settings, you need to change it on CTFd UI.Configure in UI
To configure or perform an update, Go to CTFd Admin Panel
> Plugins
> chall-manager
> Settings
, (1) select the text input, edit it, then (2) submit form, as shown below:
Warning
We strongly recommends you to NOT edit the chall-manager API URL during your event.2.2 - Challenge
Goal
Here, we assume that you are a CTF administrator, the infrastructure is already configured and you understand the key concepts. During or before your event you may need to change challenge attributes.
For all updates, go to the CTFd Admin Panel and edit the challenge (https://CTFD_URL/admin/challenges).
Change the global scope
When you arrive on the modification page, the value displayed is the one configured at the challenge level.
By editing this value, you trigger instances destruction, see workflow belows:
flowchart LR A[Update] --> B B[Update on CTFd backend] --> C C{challenge global ?} C -->|True|F[Destroy all instances] C -->|False| E[Destroy global instance] E --> H[Send update payload to CM] F --> H
Change the mana cost
When you arrive on the modification page, the value displayed is the one configured at the challenge level.
By editing this value, you do not edit the existing coupons of this challenge. Also, you can organize sales periods.
Change Timeout
When you arrive on the modification page, the value displayed is the one configured at the challenge level.
You can change or reset this value, Chall-Manager will update all the computed until
for instances.
Change Until
When you arrive on the modification page, the value displayed is the one configured at the challenge level.
You can change or reset this value, Chall-Manager will update all the computed until
for instances.
Change the scenario
When you arrive on the modification page, you can download the current scenario archive.
By editing this value, you need to provide an update strategy.
The update can be long, depends on the update gap and the strategy.
2.3 - Panel
Goal
Here, we assume that you are a CTF administrator, the infrastructure is already configured and you understand the key concepts.
During your event you may need to deploy instances for users we will see several use cases. If mana is enabled, 1 coupon will be created for each user.
Note
Even if users don’t have the required mana, the instance will be created here.Deploy instances for users
There may be several reasons why administrators create instances for users. Here, we’ll imagine that the CTF hasn’t started yet, that it’s a CTF in team mode and that 10 teams are expected. For the 10 teams, we want to deploy 1 instance of the “generated-flag” challenge.
Go to CTFd Admin Panel
> Plugins
> chall-manager
> Panel
such as:
By clicking on the button, you will send several requests to CTFd. To make the user experience easier, we add a progress bar.
Once the progress bar is done (this mean instances are created on chall-manager), you can go the instances monitoring panel.
You can use this method during your event to deploy a hidden challenge (deploy instances before the challenge is release) for instance.
Deploy a global scope instance
Here, we’ll imagine that you did configure a challenge with global scope enabled. For the global challenge, you must put the pattern at 0.
Like the previous use case, you will get a progress bar while the instance are getting deployed.
2.4 - Instances
Goal
This guide assumes you are a CTF administrator with a properly configured infrastructure and an understanding of key concepts.
During your event, you may need to monitor or manage instances associated with Sources.
How to do it
Monitoring
To monitor instances, go to the plugin settings in the CTFd Admin Panel
> Plugins
> chall-manager
> Instances
section.
Administration
You can Renew or Destroy an individual instance by clicking the corresponding button in the instance row.
To perform actions on multiple instances, (1) select the instances using the checkboxes, (2) click the associated button, (3) then confirm your choice, as shown below:
2.5 - Mana
Goal
Here, we assume that you are a CTF administrator, the infrastructure is already configured and you understand the key concepts.
During your event you may need to monitor the consumed mana for Source.
How to do it
Access the plugin settings configuration in the CTFd Admin Panel
> Plugins
> chall-manager
> Mana
3 - Design
3.1 - Architecture
Concept
As explained in the chall-manager documentation, we avoid exposing its API to prevent the risk of direct resource manipulation by players.
CTFd inherently provides functionalities like authentication, team management, scoring, and flag handling. By adding our plugin, CTFd can serve as both a challenge management platform for administrators and a request manager that acts as a proxy with user authentication, mana limitations, and more.
Overview
The basic architecture is straightforward: we have created new API endpoints for both administrators and users. These endpoints mainly handle CRUD operations on challenge instances.
API
AdminInstance
This endpoint allows administrators to perform CRUD operations on challengeId for a specified sourceId. Essentially, this endpoint forwards requests to the chall-manager for processing.
UserInstance
Unlike the AdminInstance endpoint, this one does not accept sourceId as a parameter. Instead, it automatically identifies the source issuing the request and checks mana availability before forwarding the request to the chall-manager.
UserMana
This endpoint handles GET requests to display the remaining mana of the source issuing the request.
Detailed Overview
The following diagram provides a more detailed view of how your browser interacts with the API endpoints and how these endpoints are mapped to the corresponding chall-manager endpoints.
3.2 - Challenge
Overview
The plugin introduces a new challenge type called dynamic_iac
, allowing the deployment of instances per user or team (referred to as a source). It works seamlessly with both user modes defined by CTFd (individual users or teams).
What’s new about this challenge type?
To get started, navigate to the challenge creation panel in CTFd and select the dynamic_iac
challenge type.
Here’s what the plugin adds:
Global Scope
The global scope is a boolean setting that allows a single instance to be shared among all players.
For instance, in the following setup, challenges 1, 2, and 3 have the global scope enabled, while challenge 4 does not:
In this scenario, player X (yellow) and player Y (blue) will each have their own instances for challenge 4, but will share the same instance for challenges 1, 2, and 3. We recommend enabling this feature for static, stateless applications (e.g., websites).
Mana Cost
The mana cost is an integer representing the price users must pay in mana to deploy their own instance. Mana is refunded when the instance is destroyed. This system helps control the impact users have on the platform. For more details, see how mana works.
Until
The until setting allows you to specify a date and time at which instances will be automatically destroyed by the Janitor.
Example:
As a CTF administrator running a week-long event, you want a challenge available only on the first day. Set the Janitoring Strategy to Until, and configure the end date to DD/MM/YYYY 23:59.
As a player, you can start your instance anytime before this date and destroy it whenever you like before the deadline.
Timeout
The timeout is an integer that specifies, in seconds, how long after starting an instance the Janitor will automatically destroy it.
Example:
As a CTF administrator or challenge creator, you estimate that your challenge takes about 30 minutes to solve. Set the Janitoring Strategy to Timeout and set the value to 1800 seconds (30 minutes).
As a player, once you start your instance, it will be destroyed after 30 minutes unless renewed. You can also manually destroy the instance at any time to reclaim your mana.
Tips & Tricks
You can either combine the Until and Timeout values or leave both undefined. Find more info here.Scenario Archive
The scenario is a zip archive defining the challenge deployment as a Golang Pulumi project. You can use examples or create your own using the SDK and Pulumi.
3.3 - Flag
Concept
Cheating is a part of competitions, especially cash-prize is involved. The player experience is particularly frustrating, so we try to minimize some of the common cases of cheating.
We’ve concentrated on the two most common cases, namely ShareFlag and FlagHolding:
- ShareFlag: get or share flags with an another team.
- FlagHolding: store flags to submit them on the very last moment.
How this work
We redefine the submit
method of the dynamic_iac
challenge to verify the generated flag against the current source.
Here’s the algorithm:
flowchart LR Submission --> A{Instance On ?} A -->|True| B{Instance I flag ?} A -->|False| Expired B -->|True| C{submission == I.flag} B -->|False| D{CTFd C flag ?} C -->|True| Correct C -->|False| D D -->|True| E{submission == C.flag} D -->|False| Incorrect E -->|True| Correct E -->|False| Incorrect %% I/O Submission Expired Correct Incorrect
On the submit
method, we get all informations of the instance on chall-manager.
- We want the instance ON to prevent FlagHolding and make sure the submitted flag is the one in the instance.
- We check if the instance define a flag (if you don’t use the flag variation on the sdk).
- If the instance define a flag, we check if the submission is correct.
- If the instance does not define a flag or if the submission is incorrect, we check if a flag is defined on CTFd (also use as fallback).
- Now, it is the classic behavior of CTFd.
Conclusion
Addressing the shareflag issue is crucial to maintaining fairness in CTF competitions. By redefining the submission process with methods like the dynamic_iac challenge, we ensure that only legitimate efforts are rewarded, preserving the true spirit and integrity of the competition.
FAQ
Why don’t we use the CTFd Flag system for generated flag ?
That’s simple: if we generate the flag on CTFd, each team can submit any generated flag, which doesn’t address the shareflag issue.
Why must the instance be ON to submit?
There are two main reasons for this requirement:
- To prevent ShareFlag: the instance must be up and running to retrieve the generated flag from chall-manager, ensuring flags are unique and not shared between participants.
- To prevent FlagHolding: keeping the instance active forces players to consume their mana when submitting flags, encouraging continuous participation in the competition. Additionnaly, the generated flag is valid for a running instance and will be regenerated in case of recreation.
Why the CTFd Flag is consider as “fallback” ?
As we said before, the CTFd Flag system allows users to submit the same flag. We use this system to prevent connection error or latency with chall-manager or if the generated flag is invalid for synthax error (we choose the extended ASCII so it should not happen).
3.4 - Source
Concept
As explained here, the Source refers to the team or user that initiates a request. This abstraction allows compatibility with CTFd operating in either “users” or “teams” mode, making the plugin versatile and usable in both modes.
To enable sharing across all users, the sourceId
is set to 0
. The table below summarizes the different scenarios:
user_mode | global scope | sourceId |
---|---|---|
users | FALSE | current_user.id |
teams | FALSE | current_user.team_id |
users | TRUE | 0 |
teams | TRUE | 0 |
Example workflow
Instance creation
Here an example of sourceId usage in the instance creation process:
flowchart LR A[Launch] --> B{CTFd mode} B -->|users| C[sourceId = user.id] B -->|teams| D[sourceId = user.team_id] D --> E{challenge.global_scope} C --> E E -->|True| F[sourceId = 0] E -->|False| End1 F --> End1 End1(((1)))
The rest of the workflows is detailed in mana section.
3.5 - Mana
Concept
As a CTF administrator, it’s important to manage infrastructure resources such as VMs, containers, or Kubernetes Pods effectively. To avoid overloading the infrastructure by deploying excessive instances, we introduced a “mana” system that assigns a cost to each challenge.
For instance, consider these three challenges:
- challenge1: a static web server in the web category;
- challenge2: a Unix shell challenge in the pwn category;
- challenge3: a Windows server VM in the forensic category.
Challenge3 demands more resources than Challenge2, and Challenge2 requires more than Challenge1.
Suppose each team has a mana limit of 5, with the following costs assigned:
Name | Cost |
---|---|
challenge1 | 1 |
challenge2 | 2 |
challenge3 | 5 |
In this scenario, teams can deploy instances for Challenge1 and Challenge2 simultaneously but must remove both to deploy Challenge3.
Example:
- Team 1: Focused on pwn challenges, will likely prioritize deploying Challenge2.
- Team 2: Specializes in Windows forensics, making Challenge3 their main priority.
This system helps administrators control the use of resources on CTF infrastructures.
Combined with the flag variation engine feature of chall-manager, mana also helps minimize Flag Holding. To progress, players must submit flags and destroy instances to reclaim mana. If players choose to hoard flags until the last moment, mana limits their ability to continue, adding a strategic element to the competition.
How it works
Mana Total
To set the total mana available to users, go to the Settings section of the plugin and adjust the “Mana Total” field. By default, this feature is disabled with a value of 0. The total mana can be modified during the event without any impact on existing configurations.
Mana Cost
Each dynamic_iac
challenge is assigned a mana cost, which can be set to 0 if no cost is desired. Changes to mana costs can be made during the event, but previously issued coupons will retain the cost at the time of their creation.
Coupons
When a user requests an instance, a coupon is generated as long as they have enough mana. Administrators can bypass restrictions and deploy instances directly through the admin panel, creating coupons without limits. Each coupon stores the sourceId, challengeId, and challengeManaCost at the time of creation.
Example workflow
Instance creation
Here an example of the usage of mana and the coupon creation while the instance create process:
flowchart LR End1(((1))) End1 --> G{Mana is enabled ?} G -->|True|H{Source can afford ?} G -->|False|I[Deploy instance on CM] H -->|True|J[Create coupon] H -->|False|End J --> I I --> K{Instance is deployed ?} K -->|True|M[End] K -->|False|N{Mana is enabled?} N -->|True|L[Destroy coupon] N -->|False|M L --> M
Detailed process:
- We check that Mana Total is greater than 0.
- If mana is enable, we check that the Source can afford the current Mana Cost.
- If Source cannot afford the instance, the process end.
- If Source can afford, we create a coupon.
- While the coupon is created, or the mana feature is disable, we create the instance on chall-manager.
- We check that the instance is running on Chall-Manager.
- If it’s running correctly, we end the process.
- If not, we destroy the coupon if exists, then end process with an error.
Synchronicity
Due to the vital role of mana, we have to ensure its consistency: elseway it could be possible to brute-force the use of Chall-Manager to either run all possible challenges thus bypassing restrictions.
To provide this strict consistency, we implemented a Writer-preference Reader-Writer problem based upon Courtois et al. (1971) work. We model the problem such as administration tasks are Writers and players Readers, hence administration has priority over players. For scalability, we needed a distributed lock system, and due to CTFd’s use of Redis, we choosed to reuse it.
FAQ
Why aren’t previous coupon prices updated?
We modeled the mana mechanism on a pricing strategy similar to retail: prices can fluctuate during promotions, and when an item is returned, the customer receives the price paid at purchase. This approach allows CTF administrators to adjust challenge costs dynamically while maintaining consistency.
Why can administrators bypass the mana check?
During CTF events, administrators might need to deploy instances for users who have exhausted their mana. While coupons will still be generated and mana consumed, administrators can set the cost to 0 at the challenge level to waive the charge if needed.
3.6 - Testing
Purpose
Developing in an Open Source ecosystem is good, being active in responding to problems is better, detecting problems before they affect the end-user is the ideal goal!
Following this philosophy, we put a lot of effort into CI testing, mainly using Cypress, and analyze code security via CodeQL.
GitHub Actions
From a technology point of view, we use GitHub Actions whenever possible to automate our tests.
Security
To ensure ongoing security, we enable advanced security analysis on the repository and conduct periodic scans with CodeQL on each pull request.
Testing
Our tests are systematically executed on each push and pull request, following a two-stage process.
The first stage involves Cypress for plugin integration, where we validate correct test cases to ensure comprehensive coverage. The second stage is executed to check for potential error with invalid cases. This stage runs after the Cypress tests to save time and due to technical constraints, such as the creation of challenges (preconditions).
To detect potential divergences with CTFd mode, we execute the tests in mode: users
and mode: teams
.