Sea vector created by makyzz - www.freepik.com

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.

Background

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 ubuntu image.

sudo docker pull ubuntu

Run a container in docker using the ubuntu image.

sudo docker run -it ubuntu bin/bash

The -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 luciafer.

adduser luciafer

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 spookyboi.

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.

adduser 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 NOPASSWD configuration.


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.

The --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 /etc/sudoers file.

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 /etc/passwd.

sudo usermod --shell /usr/bin/dsh syyntax

Alternatively, you can change the user's shell directly in /etc/passwd.

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.

Modify the /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:

ClientAliveInterval 3600

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.

ssh hacktober@env.hacktober.io

Conclusion

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 jason.scott@cyberhacktics.com or on Twitter at https://twitter.com/CHacktics.