Setting up OpenVPN for CTF

Once, a task was set to create a custom stand for holding CTF competitions. The tasks were prepared in the format of Docker containers, but they required a network.

The logical question arises: “Why?” The network will allow you to allocate a separate IP address to each task. In principle, of course, you can do without a network, but this approach has several disadvantages, namely:

  • Services have non-standard ports.

  • All tasks are deployed to one address, so the important task of scanning ports is removed from the task. Participants will immediately know them, and it will only remain to specify the service.

  • Manually distributing ports. In my tasks, there was mostly more than one service per task. So there is a need for logical separation of tasks. And there may also be problems with automatic task launch.

  • The tasks involve using a reverse shell, and if the service is deployed on the Internet, users will not be able to use it without a white address. It turns out that 99% of users will not be able to solve the task without using, for example, a VPS

  • I would like to restrict access to the Internet for tasks so that after a successful attack the user cannot use the machine's resources for his own purposes.

Briefly about what I wanted to get:

  • Tasks should be in an isolated network, without Internet access, but if necessary, it should be possible to issue it.

  • Each participant must have an individual IP address.

  • Each task must have an individual IP address.

  • There should be no restrictions on the ports used by each task.

  • After solving the problem, the user should not use the resources for his own purposes.

  • One VPN config for all competition participants.

    The article covers the possibility of working with both Docker containers and virtual machines. However, if any point is not needed, it can be skipped.

Software and hardware

Proxmox 8.1.4 was used as a hypervisor, and a Debian 12 virtual machine was used as a router.

To understand the structure of the project, the network diagram is presented below:

If your network uses these address ranges, then when creating such an infrastructure, it makes sense to replace them, as you may encounter a problem.

If your network uses these address ranges, then when creating such an infrastructure, it makes sense to replace them, as you may encounter a problem.

The question may arise: “Why OpenVPN?”

Wireguard could be considered as an alternative, however:

  • Running its configs is a bit more complicated for the user.

  • OpenVPN also allows you to connect multiple clients using one config.

  • OpenVPN is already a standard in CTF competitions.

Basic setup. System update

Debian 12 was taken as a basis. We update the system and install software for basic work:

sudo apt update && sudo apt upgrade -y && sudo apt install -y curl vim nano wget net-tools iptables-persistent

If it offers to save iptables rules, select “Yes”

If it offers to save iptables rules, select “Yes”

IP Forward

Next, you need to enable IP forward in the system:

sudo nano /etc/sysctl.conf

Edit the parameter: net.ipv4.ip_forward = 1.You need to set the value to 1

Static address on the network

Since our device will act as a server and router, getting an address via DHCP will not be the best solution. Therefore, it is necessary to specify a static address:

Static address

Static address

Edit the file /etc/network/interfaces in any convenient text editor:

sudo nano /etc/network/interfaces

Let's add the following lines to configure a static IP address:

auto ens18

iface ens18 inet static address 192.168.0.155

netmask 255.255.255.0

gateway 192.168.0.1

dns-nameservers 8.8.8.8

Example config

Example config

After rebooting the server will receive the address 192.168.0.155

sudo systemctl restart NetworkManager.service

Installing OpenVPN

This project was taken as a basis: https://github.com/kylemanna/docker-openvpn.

But we will deploy it immediately on the main system for ease of working with the rules:

  • Let's change the user to root: sudo su -

  • Install the necessary packages: sudo apt install -y openvpn iptables bash git easy-rsa pamtester

  • Let's write a link to use files from /usr/share/easy-rsa/easyrsa

    ln -s /usr/share/easy-rsa/easyrsa /usr/local/bin

  • Let's register environment variables for the scripts to work:

    echo "export OPENVPN=/etc/openvpn" >> ~/.bashrc

    echo "export EASYRSA=/usr/share/easy-rsa"
    >> ~/.bashrc

    echo "export EASYRSA_CRL_DAYS=3650" >> ~/.bashrc echo "export EASYRSA_PKI=$OPENVPN/pki" >> ~/.bashrc

  • Activate the config ~/.bashrc to make the environment variables active: source ~/.bashrc

  • We clone the repository and go to the directory: git clone https://github.com/kylemanna/docker-openvpn cd docker-openvpn

  • We copy the necessary files and grant rights:

    cp ./bin/* /usr/local/bin

    cp ./otp/openvpn /etc/pam.d/ chmod a+x /usr/local/bin/* cd

  • Next, we perform the configuration. Instead of (HERE_YOU_NEED_SPECIFY_THE_SERVER_IP_ADDRESS), we specify the server's IP address:

    ovpn_genconfig -N -d -u udp://ТУТ_НУЖНО_УКАЗАТЬ_IP_АДРЕС_СЕРВЕРА

For example like this

For example like this

At this point you need to come up with and enter a key

At this point you need to come up with and enter a key

  • Example key: Qwertyqwertyqwerty123

    Next, follow the text instructions:

  • Generate configs for participants. When creating, you will be asked for the key we entered above: easyrsa build-client-full ctf-team nopass

  • Save the config to a file: ovpn_getclient ctf-team > ./ctf-team.ovpn

    Since we need participants to be able to connect to the network through one config, let's edit the config: vim /etc/openvpn/openvpn.conf

  • Add the line: duplicate-cn

Example

Example

  • Launch OpenVPN: ovpn_run

  • Download the server config from the path /root/ctf-team.ovpn to your computer.

    It might look like this:scp root@SERVER_IP:/root/ctf-team.ovpn ~

    And we try to launch it.

  • Launches on the main system: sudo openvpn ctf-team.ovpn

Let's check that we have the tun interface

Let's check that we have the tun interface

If everything works, then we try to restart the server and check again. OpenVPN should start on its own.

Setting up Docker

Let's install Docker: sudo apt install -y docker.io docker-compose

Network configuration

We go to the console again so that the rights are updated, and create a docker network: docker network create --subnet=192.168.158.0/24 ctf-net

You can read more about working with Docker network here: https://habr.com/ru/companies/otus/articles/730798/

  • Open the VPN config that we downloaded from the server and add the following lines to it:

    route 192.168.255.0 255.255.255.0

    route 192.168.158.0 255.255.255.0

Example

Example

If these two lines are present, then everything is done correctly:

192.168.158.0/24 via 192.168.255.5 dev tun0

192.168.255.0/24 via 192.168.255.5 dev tun0

iptables rules

We write rules for iptables:

sudo iptables -A FORWARD -i tun0 -s 192.168.255.0/24 -d 192.168.158.0/24 -j ACCEPT

sudo iptables -A FORWARD -o tun0 -s 192.168.158.0/24 -d 192.168.255.0/24 -m state --state RELATED,ESTABLISHED -j ACCEPT

# Разрешим трафик от машин (192.168.158.0/24) к сети (192.168.255.0/24) sudo iptables -A FORWARD -o tun0 -s 192.168.158.0/24 -d 192.168.255.0/24 -j ACCEPT

sudo iptables -A FORWARD -i tun0 -s 192.168.255.0/24 -d 192.168.158.0/24 -m state --state RELATED,ESTABLISHED -j ACCEPT

Network tests

Network tests before container

  • On the server, we'll set up a test container with nginx: docker run --network=ctf-net -d --rm --name nginx_test nginx

  • After which we get the address of this container in the docker network: docker inspect nginx_test | grep IPAddress

In this case it is 192.168.158.2 so on the main system with the VPN connected in the browser I go to the site 192.168.158.2

The container site is open

The container site is open

Network test from container

Let's check in the opposite direction.

I get my address through the ifconfig command.

I get my address through the ifconfig command.

  • I get my address through the ifconfig command: docker exec -ti nginx_test /bin/bash

  • After which I get the console inside the container from the root user.

    By default, there is no ping in the basic nginx container, so it needs to be installed: apt update && apt install iputils-ping

  • After that we check the clients' access to the VPN:

Checking Client Access to VPN

Checking Client Access to VPN

Everything works, so we exit the container with the command exit

Save iptables rules

Now you need to save the iptables rules.

Launching CTF tasks

Everything is simple here: any container launched with the parameter: --network=ctf-netwill immediately be configured and will work with the network we need.

So, running tasks will look like this: docker run --network=ctf-net -d --rm --name CONTAINER_NAME IMAGES_NAME

Setting up access to an isolated network of virtual machines

Docker is great, but tasks can also be deployed to virtual machines, so you need to enable them too.

  • To do this, we will create a virtual network in our hypervisor.

  • Then we connect our machine to this network with a new network interface.

After the settings, our interfaces look like this

After the settings, our interfaces look like this

Here we can see that ens19 is left without an address, since there is no DHCP server in the isolated network. Therefore, it is necessary to register a static address on the interface and install a DHCP server.

The config should look like this

The config should look like this

After reboot

After reboot

Installing DHCP (This module can be collapsed)

sudo apt install isc-dhcp-server

After installation we will get an error that the service could not start.

After installation we will get an error that the service could not start.

  • Editing the config: sudo nano /etc/dhcp/dhcpd.conf

  • We add this to the empty space:

    subnet 10.0.50.0 netmask 255.255.255.0 {

    range 10.0.50.10 10.0.50.240;

    option domain-name-servers 8.8.8.8;

    option subnet-mask 255.255.255.0;

    option routers 10.0.50.1;

    default-lease-time 600;

    max-lease-time 7200;

    }

The result should look like this

The result should look like this

***I would like to draw attention to the range 10.0.50.10 10.0.50.240 .

Here the range of IP addresses distributed by the DHCP server is set. That is, 2-10 and 240-254 can be used as service addresses. For example, monitoring services or services with a mandatory static address.

  • Edit the following file: sudo nano /etc/default/isc-dhcp-server

  • You need to specify the interface to which the DHCP server will distribute addresses.

Example

Example

Starting the service

Starting the service

Now it's time to test it out.

Machine got address but 8.8.8.8 doesn't ping

Machine got address but 8.8.8.8 doesn't ping

iptables rules

Add the route to the OpenVPN config that you downloaded from the server.

Now it looks like this

Now it looks like this

  • We write rules for iptables. (Run as root)

    Please note the name of the interfaces. They will probably be called differently.

    iptables -A FORWARD -i tun0 -o ens19 -s 192.168.255.0/24 -d 10.0.50.0/24 -j ACCEPT

    iptables -A FORWARD -i ens19 -o tun0 -s 10.0.50.0/24 -d 192.168.255.0/24 -m state --state RELATED,ESTABLISHED -j ACCEPT

    # Разрешим трафик от машин (10.0.50.0/24) к сети (192.168.255.0/24) iptables -A FORWARD -i ens19 -o tun0 -s 10.0.50.0/24 -d 192.168.255.0/24 -j ACCEPT

    iptables -A FORWARD -i tun0 -o ens19 -s 192.168.255.0/24 -d 10.0.50.0/24 -m state --state RELATED,ESTABLISHED -j ACCEPT

  • After that, we try to ping the client connected to the VPN from the virtual machine:

Pinging the client

Pinging the client

From the client we ping the virtual machine

From the client we ping the virtual machine

  • We keep the new rules: iptables-save > /etc/iptables/rules.v4

    At this point, this stage of configuration for virtual machines can be considered complete.

Additional iptables rules

  1. Pay attention to the interface names. They will probably be called differently.

  2. It is advisable to make a backup of the virtual machine so as not to accidentally break anything.

  • Let's allow connections via ssh: sudo iptables -A INPUT -i ens18 -s 192.168.0.1/24 -p tcp --dport 22 -j ACCEPT

  • Let's open access to the VPN udp port: sudo iptables -A INPUT -i ens18 -p udp --dport 1194 -j ACCEPT

  • Let's apply the default policy: sudo iptables -P FORWARD DROP

  • Internet access ban for tun:

iptables -I FORWARD -i tun0 -o ens18 -j DROP

iptables -I FORWARD -i ens18 -o tun0 -j DROP

Internet Ban for Docker

  • The container has the ability to access the Internet by default, but we are not happy with this, so we need to add a rule prohibiting this:

    iptables -I FORWARD -s 192.168.158.0/24 -j DROP

    iptables -I FORWARD -s 192.168.158.0/24 -d 192.168.255.0/24 -j ACCEPT

Everything is working

Everything is working

Internet access ban for VM

Since we do not have rules allowing traffic to move to the Internet, the virtual machine will not be able to access the Internet by default.

Internet permission for Docker

To allow internet access, you need to remove the rule.

  • This can be done like this: iptables -D FORWARD -s 192.168.158.0/24 -j DROP

  • Revert the rule back and block access to the Internet:

    iptables -D FORWARD -s 192.168.158.0/24 -d 192.168.255.0/24 -j ACCEPT

    iptables -I FORWARD -s 192.168.158.0/24 -j DROP

    iptables -I FORWARD -s 192.168.158.0/24 -d 192.168.255.0/24 -j ACCEPT

Internet access permission for VM

This is a bit more complicated. You need to activate packet forwarding, which can be done using MASQUERADE. And also allow packet forwarding for addresses 10.0.50.0/24

iptables -t nat -A POSTROUTING -o ens18 -s 10.0.50.0/24 -j MASQUERADE

iptables -A FORWARD -i ens19 -o ens18 -s 10.0.50.0/24 -j ACCEPT

iptables -A FORWARD -i ens18 -o ens19 -d 10.0.50.0/24 -j ACCEPT

Please note that ens18 is the name of the interface that looks at the Internet.

Also the reverse task, namely, to return the ban on traffic:

iptables -t nat -D POSTROUTING -o ens18 -s 10.0.50.0/24 -j MASQUERADE

iptables -D FORWARD -i ens19 -o ens18 -s 10.0.50.0/24 -j ACCEPT

iptables -D FORWARD -i ens18 -o ens19 -d 10.0.50.0/24 -j ACCEPT

Saving the rules

After the changes we needed, we saved the rules: iptables-save > /etc/iptables/rules.v4

Since when you reboot, rules that were not saved will be automatically deleted, this is very convenient to use.

On this stand, containers and virtual machines do not have Internet access by default, but if I need to download something for configuration, I simply write a rule for this, and after work I turn off the stand.

As a result, when the team comes to attack the stand, there is no Internet on the tasks according to the standard, and there is no need to remember whether there was permission to enter or not.

Conclusion and some thoughts

It won't be difficult to assemble a stand for holding CTF competitions or experiments.

Initially, the project was made to hold competitions
at the university, but in the end it turned out
really convenient, and several copies were deployed for different tasks.

(One is even at home, for local experiments with friends. Since you can connect to such a network remotely and have your own stand for training.)

As mentioned earlier, there were competitions at the institute, so I really wanted to monitor the load in real time. The competition was held entirely on Docker containers, and the domolo project is perfect for tracking their resources: https://github.com/ductnn/domolo.git

Just download and run via docker-compose. As a result, we have statistics on resources for each container. So, if someone runs a conditional miner in a container, it will be immediately visible, and this container can be quickly destroyed.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *