The challenge toolbox is a small component which allows you to create, run and check new avatao challenges locally. If you have any questions don't hesitate to contact us at [email protected].
Avatao is a company that wants to make hands-on training accessible to everyone. This includes open-source communities and other businesses also.
- Ownership of challenges: We released this tool to help people to run their own exercises locally and share with others.
- Open-source licensing: The value to the community contribution is huge - each one is given in service and respect to the community. To respect this, we release the challenge toolbox and the related templates under Apache 2.0 License. We do not restrict the use of derived challenges as long as they are contributed back under the same license.
- Business services: Avatao provides a 24/7 runtime environment that allows you to expose your challenge online in an easy way. Note that Avatao provides additional services (e.g., custom-tailored challenges for businesses, community organizations, enterprise support, training programs, customized learning) as part of a business offering.
- Docker
- Python 3
- Run
pip3 install -r requirements.txt
- Choose a challenge template from the
templates
directory - Build challenge:
./build.py <challenge_folder>
(e.g.,python3 ./build.py templates/xss
) - Start challenge:
./start.py <challenge_folder>
(e.g.,python3 ./start.py templates/xss
) - Check challenge format:
./check.py <challenge_folder>
(e.g.,python3 ./check.py templates/xss
) - Cleanup:
./docker-cleanup.sh
So as to ease challenge development we've prepared templates for different challenge types using our base images (e.g., debian, ubuntu, controller, exploitation). These templates contain examples for static and container-based challenges. Please, use the one which fits your needs and customize it as you like.
- Static challenges without containers
- Challenges running a server and accept client connections via telnet
- Challenges accepting connections via SSH
- C programming challenges
- C# programming challenges
- Java programming challenges
- XSS challenges
By cloning this repository you get scripts and skeleton files that will help you a lot in challenge creation.
To build your challenge images (e.g., solvable, controller) please use our build script as follows.
./build.py <repository_path>
For example, if your repository path is /home/user/my-challenge
, your images will be called
my-challenge-solvable
and my-challenge-controller
, if you have Dockerfile
for both controller and solvable.
If you do not want to build both images, just simply modify the name of image (e.g., solvable_
) you do not need now and the script skips it.
Only the first build is slow. If you modify any file in those directories, you need to run a rebuild before starting them.
If the build process was successful you should see your new docker image by typing the following command:
docker images
To start your challenge, simply type:
./start.py <repository_path>
When a controller-solvable pair is started, you can address them internally as localhost
, thus no IP address is required. This is useful, for example, when you want to access the solvable with a solution checking script from the controller container. See the example for more details.
In the next step, please modify the cloned template in accordance with your challenge. If you have prepared everything, run our checker script of this guide repository by simply typing:
./check.py <repository_path>
For example, if your challenge is located at path /home/user/my-challenge
, your command is the following.
./check.py /home/user/my-challenge
Note that check.py
will fail until you start your challenge, however, it is good to verify if everything is in place.
Please, fine-tune your files until you see errors. This script will help you a lot:
- Checks if your files are in place.
- Checks if your configuration (config.yml) and markdown files (e.g., writeup.md) are correct.
- Invokes solution check and the test function automatically for docker-based challenges to check if your challenge is working well.
We can automatically test if the challenge is working properly. To do that, you need to implement the test
function in the controller's server.py
. You can find here an example test
function implementation for C programming challenges.
After implementing the test
function you can simply run curl
to make sure that everything works well.
curl 127.0.0.1:5555/secret/test
Similarly to solution checking, the test
function returns with HTTP 200 status code if all the tests passed and
returns with the build log. Otherwise the HTTP status code is 500.
Sometimes it is worth removing stalled and dangling images. To do that simply run our clean-up script:
./docker-cleanup.sh
We have prepared many useful packages and dependencies in our base images and templates, but you might need to get your hands dirty and add extra stuff. To help you, we summarized the most important best practices you should know about docker.
-
If you need to install extra packages to controller use the following template:
FROM avatao/controller:ubuntu-14.04 USER root RUN apt-get -qy update \ && apt-get install -qy <your_package> USER user COPY ./controller/ /
-
The
USER user
docker command should be used in the solvableDockerfile
if a service can run without root privileges. -
Please, make sure that none of the user-controllable services run with root privileges. For example, an SSH daemon can run as root but it should only allow non-privileged users to log in.
-
The
COPY
docker command is the preferred way to add your files directories to a container. It has various advantages overADD
: 1) simple, 2) preserves file attributes, 3) do not decompress archives or fetch URLs. -
Minimize filesystem layers by reducing the number of
RUN
commands. -
If you do not delete something in the same
RUN
command it was created in, it will still be part of the final image in an internal layer. -
The
find /bin /sbin /usr -perm /6000 -exec chmod -s '{}' \;
command disables suid flags on system files so we could safely keep theSETGID
andSETUID
capabilities for a challenge.
There are various runtime docker options that we plan to turn on when starting your challenge with the docker run
command.
- The
--read-only
option mounts the container's root filesystem as read only. As certain challenges require to have writable directories and files, you can add volumes with theVOLUME
docker command. For example we added the following volumes for LAMP base images by default.VOLUME ["/etc/mysql", "/var/lib/mysql", "/etc/php5/apache2", "/var/log", "/run"]
- By default we drop all the capabilities with
--cap-drop=ALL
as you can see instart.py
. To add extra capabilities please use the capabilities field ofconfig.py
. If you do not need extra capability just simply leave the array empty (['']).
Reference:
- https://docs.docker.com/engine/reference/builder/
- https://docs.docker.com/engine/reference/commandline/run/
- https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
- https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/
It might take too much time to keep waiting for build.py
and start.py
to finish when you want to debug your challenge.
It is much simpler to start your challenges with start.py
and the run :
docker exec -ti <name> bash
For example, if you have problems with implementing the test endpoint in the controller, it is better if you simply
copy server.py
at a writeable path (e.g., added with the VOLUME
command) and edit locally with vim
for example.
IMPORTANT Don't forget to save your changes outside the container regularly, as all your modifications will be lost
if you stop start.py
.
In order to avoid trivial user cheats flags can be dynamically generated every time a challenge is started. Here are two options:
-
flag = static part + random part: In this case, a fix static part is extended with a suffix randomly generated whenever the challenge is started.
-
totally random flag: In the code below, we store an entirely random flag in the solvable's
/var/flag/flag.txt
file. This file is also accessible for the controller container.controller/opt/server.py
#random flag generation with open("/home/user/flag.txt","w") as f: f.write("Ea5Y_Pea5Y_") for x in random.sample(range(1, 5000), 8): f.write(str(x)) if x%2 == 0: f.write("_/") # solution checking def solution_check(): submitted_solution = request.json['solution'] if submitted_solution != open('/var/flag/flag.txt').read(): return jsonify(solved=False) return jsonify(solved=True)
Please read the docs for more information.
ssh_exchange_identification: Connection closed by remote host
You run a command in solvable which blocks the start of SSH daemon. Fix the problematic command, and rebuild. This weird behavior is observed because docker accepts connection attempts to published ports but then resets them if the published port isn't really listening.
Just build the container with build.py
, check the built image with docker image
and run:
docker run -ti --rm <repository|ID> bash
The container will get removed, when you exit it.
If sshd's non-root login is not enough for you. You can still use docker exec
. To use docker exec
, find first the hash of your running container using
docker ps
After that simple execute the following command
docker exec -ti <name|ID> bash