This past weekend, Cyber Hacktics, in partnership with CyberUp, ran a competition called Hacktober CTF in support of National Cyber Security Awareness Month. The CTF was the fruit of many, many hours of development and organization, but I'll cover that in a future post.
One of the most-asked questions I received from players after the competition was how we set up our Talking to the Dead series of challenges. In these challenges, players connected to a remote host over SSH and were dropped into a unique Docker container.
In this blog, I'll go over step-by-step instructions showing how I created the Talking to the Dead Linux challenges.
When creating a CTF, it's usually a bad idea to let players interact in an environment where they can bump into each other. I've seen this go wrong when one player will SSH into a machine then change or delete the flag to make it impossible for other players to get points. To remedy this, you can setup a machine that drops everyone into their own unique container where they're safe from other players.
Building the Docker Image
First, you'll need to build a docker container that you want people to access. I used my home lab which uses ProxMox for virtualization - but you can use any virtualization solution to create your docker image. You can find docker installation procedures for Ubuntu here.
After you have docker installed, go ahead and pull an image. Start with something simple like a standard
sudo docker pull ubuntu
Run a container in docker using the
sudo docker run -it ubuntu bin/bash
-it option runs the docker container with a psuedo-tty and lets you interact with the container by dropping you into the container's BASH shell.
From here, make all your custom modifications. Hide flags, add files, or anything else that you want users to interact with. In my example, I'll create a basic user from within the container called
Once you're done, exit the container and commit your changes. Use
docker ps -a to get the container's ID. In this example, we'll name our image
sudo docker commit [CONTAINER ID] spookyboi
If you have your own public or private registry, go ahead and push it. If you have a registry on Docker Hub, you can use a command similar to the one below.
sudo docker push <username>/spookyboi
And that's it! Your modified docker image is hosted in your registry and ready for the next step.
Setting Up the Environment
Pull the Docker Image
Next, you'll need to setup a machine that users will be able to connect to remotely. The machine should have docker installed. Once it's installed, pull down that docker image you created in the previous step from your registry. If you're using Docker Hub, indicate the username. If not, use the registry URL.
sudo docker pull <username|registry>/spookyboi:latest
Create a User
From the host machine (not the docker container), create a local user that people will SSH into. In this example, the user will be named hacktober.
Modify the Sudoers File
Now, in order to run Docker, the hacktober user will need superuser (root) privileges. You don't want to give the user full superuser privileges, so modify the
/etc/sudoers file to grant the user only escalated privileges to run a very specific docker command. You also don't want the machine to ask for the password to run the command, so give it the
hacktober ALL=(ALL) NOPASSWD: /usr/bin/docker run -it --rm --user luciafer <username|registry>/spookyboi:latest
Let's talk about the information. We're telling the system that the local user luciafer has privileged permissions to run
/usr/bin/docker run -it --rm --user luciafer <username|registry>/spookyboi:latest without requiring the user's password. The
--rm option is given so that when the user exits the SSH session, their container is removed so that it won't take up space on the machine.
--user spookyboi option tells docker to enter the container as the user
spookyboi. Without this option, the user will enter the container as root and will be able to do anything within the docker container.
Create a Shell
Typically, when users SSH to a remote machine, they enter a BASH or other shell. For the sake of what we're doing, we want the remote user to drop into a unique docker container. To do that, we're going to create a Python script that the user will drop into rather than BASH or any other shell.
Create the following Python 3 script and name it
dsh (docker shell).
/usr/bin/python3 import os os.system('sudo /usr/bin/docker run -it --rm user luciafer <username|registry>/spookyboi:latest')
It's important that the
system function reads exactly the same as the command we entered in the
Save the script and change its permissions.
sudo mv dsh /usr/bin/dsh sudo chmod +x /usr/bin/dsh
Change the User's Shell
Finally, you'll need to modify the hacktober user's default shell in
sudo usermod --shell /usr/bin/dsh hacktober
Alternatively, you can change the user's shell directly in
Set SSH Sessions to Timeout
This is a very important lesson I learned during the Hacktober CTF. Some users leave their SSH sessions open even when they're done using them. This, in turn, keeps the docker container open, which means space is being taken up and not being used.
During Hacktober, I set my remote machine to timeout after one hour. That way, if players left their SSH sessions idling, it would disconnect them after an hour and terminate their docker container as well.
/etc/ssh/sshd_config file by locating the line that reads
#ClientAliveInterval. Remove the
# and add
3600 at the end. This means the SSH session will timeout after 3,600 seconds (or 1 hour). Your line should look something like this:
Now, restart your SSH service
sudo systemctl reload sshd
Now You're Ready!
Alright, now go ahead and try it out! Open a separate terminal and SSH into your remote machine with the user hacktober. In my example, my remote machine was setup at env.hacktober.io.
This was the first time I attempted to create an environment where each CTF player would get their own unique docker container. There may be other, more efficient ways to accomplish this, but it served its purpose for what I needed. Allowing players to have their own space to complete challenges enables them to do their work unhindered by other players.
One important takeaway when using this method is to properly scale your environment. Originally, I was using a server with 2 CPUs, 4GB RAM, and 60GB HDD space. With over 2,000 players, I quickly ran out of processing power and HDD space, so I had to upgrade to 4 CPUs and 8GB RAM which worked out perfectly.
If you know a better way to implement remote private, unique docker containers, I would love to hear your suggestions! You can reach me at firstname.lastname@example.org or on Twitter at https://twitter.com/CHacktics.