Однажды была поставлена задача создать собственный стенд для проведения CTF-соревнований. Задачи были подготовлены в формате Docker-контейнеров, но для них была необходима сеть.

Возникает логичный вопрос «Зачем?». Сеть позволит выделить отдельный IP адрес каждой задаче. В принципе, конечно, можно обойтись и без сети, однако такой подход имеет несколько минусов, а именно:

  • Службы имеют нестандартные порты.

  • Все задачи развернуты на одном адресе, так что из задания уходит важная задача по сканированию портов. Участники сразу будут их знать, и останется только уточнить службу.

  • Раздача портов вручную. В моих задачах по большей части был не один сервис на задачу. Так что возникает необходимость логического разделения заданий. А также могут быть проблемы с автоматическим запуском задач.

  • Задачи подразумевают использование reverse shell, а если сервис разворачивать в интернете, то пользователи не смогут его использовать без белого адреса. Получается, что 99% пользователей не смогут решить задачу без использования, например, VPS.

  • Хотелось бы ограничить доступ в интернет задачам. Чтобы после проведения успешной атаки пользователь не смог использовать ресурсы машины в своих целях.

Кратко о том, что хотелось получить:

  • Задачи должны быть в изолированной сети, без доступа в интернет, но при необходимости должна быть возможность его выдать.

  • Каждый участник должен иметь индивидуальный IP адрес.

  • Каждая задача должна иметь индивидуальный IP адрес.

  • Не должно быть ограничений на использование портов у каждой задачи.

  • После решения задачи пользователь не должен использовать ресурсы в своих целях.

  • Один конфиг VPN для всех участников соревнования.

    В статье рассматривается возможность работы как с Docker-контейнерами, так и виртуальными машинами. Но, если какой-то пункт не нужен, его можно пропустить.

Софт и железо

В качестве гипервизора использовался Proxmox 8.1.4, а в качестве маршрутизатора — виртуальная машина Debian 12.

Для понимания структуры проекта ниже представлена схема сети:

Если в вашей сети используются эти диапазоны адресов, то при создании подобной инфраструктуры, имеет смысл их заменить, так как можно столкнуться с проблемой.
Если в вашей сети используются эти диапазоны адресов, то при создании подобной инфраструктуры, имеет смысл их заменить, так как можно столкнуться с проблемой.

Может возникнуть вопрос: «Почему именно OpenVPN?».

Как альтернативу можно было бы рассмотреть Wireguard, однако:

  • Запуск его конфигов несколько сложнее для пользователя.

  • Также OpenVPN позволяет подключить несколько клиентов по одному конфигу.

  • OpenVPN уже является стандартом во время проведения CTF-соревнований.

Базовая настройка. Обновление системы

За основу была взята Debian 12. Обновляем систему и ставим софт для базовой работы:

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

Если предлагает сохранить правила iptables – отмечаем «Yes»
Если предлагает сохранить правила iptables – отмечаем «Yes»

IP Forward

Далее надо включить IP forward в системе:

sudo nano /etc/sysctl.conf

Редактируем параметр: net.ipv4.ip_forward = 1.Нужно поставить значение 1

Статический адрес в сети

Так как наше устройство будет выполнять роль сервера и маршрутизатора, то получать адрес по DHCP будет не самым правильным решением. Следовательно, необходимо прописать статический адрес:

Статический адрес
Статический адрес

Редактируем файл /etc/network/interfaces в любом удобном текстовом редакторе:

sudo nano /etc/network/interfaces

Добавим следующие строки для настройки статического IP адреса:

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

Пример конфига
Пример конфига

После перезагрузки сервер получит адрес 192.168.0.155

sudo systemctl restart NetworkManager.service

Установка OpenVPN

За основу был взят этот проект: github.com/kylemanna/docker-openvpn

Но разворачивать его будем сразу на основной системе для удобства работы с правилами:

  • Сменим пользователя на пользователя root: sudo su -

  • Устанавливаем нужные пакеты: sudo apt install -y openvpn iptables bash git easy-rsa pamtester

  • Пропишем ссылку для использования файлов из /usr/share/easy-rsa/easyrsa

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

  • Пропишем переменные окружения для работы скриптов:

    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

  • Активируем конфиг ~/.bashrc , чтобы переменные окружения стали активны: source ~/.bashrc

  • Клонируем репозиторий и переходим в директорию: git clone https://github.com/kylemanna/docker-openvpn cd docker-openvpn

  • Копируем нужные файлы и выдаем права:

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

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

  • Далее выполняем конфигурацию. Вместо (ТУТ_НУЖНО_УКАЗАТЬ_IP_АДРЕС_СЕРВЕРА), указываем IP адрес сервера:

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

Например так
Например так
  • Генерируем ключи: ovpn_initpki

В этот момент нужно придумать и ввести ключ
В этот момент нужно придумать и ввести ключ
  • Пример ключа: Qwertyqwertyqwerty123

    Далее следуем текстовым инструкциям:

  •  Генерируем конфиги для участников. При создании будет запрошен ключ, который мы вводили выше: easyrsa build-client-full ctf-team nopass

  • Сохраняем конфиг в файл: ovpn_getclient ctf-team > ./ctf-team.ovpn

    Так как нам нужно, чтобы участники могли подключаться через один конфиг к сети, отредактируем конфиг: vim /etc/openvpn/openvpn.conf

  • Добавляем строку: duplicate-cn

Пример
Пример
  • Запускаем OpenVPN: ovpn_run

  • Скачиваем конфиг сервера по пути /root/ctf-team.ovpn к себе на компьютер.

    Это может выглядеть так:scp root@SERVER_IP:/root/ctf-team.ovpn ~

    И пробуем запустить.

  • Запускам на основной системе: sudo openvpn ctf-team.ovpn

Проверяем, чтобы у нас появился интерфейс tun
Проверяем, чтобы у нас появился интерфейс tun

Если все работает, то пробуем перезапустить сервер и проверяем снова. OpenVPN должен запуститься самостоятельно.

Настройка Docker

Установим Docker: sudo apt install -y docker.io docker-compose

  • Выдаем права пользователю на работу с docker: sudo usermod -aG docker НАШ_ЮЗЕР

Настройка сети

Вновь заходим в консоль, чтобы права обновились, и создаем docker сеть: docker network create --subnet=192.168.158.0/24 ctf-net

Подробнее про работу с Docker network можно почитать тут.

  • Открываем конфиг для VPN, который мы скачали с сервера, и добавляем в него следующие строки:

    route 192.168.255.0 255.255.255.0

    route 192.168.158.0 255.255.255.0

Пример
Пример
  • После чего подключаемся к OpenVPN на своем компьютере и вводим: ip route

Если есть эти две строки, то все сделано правильно:

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

Прописываем правила для 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

Тесты сети

Тесты сети до контейнера

  • На сервере поднимем тестовый контейнер с nginx: docker run --network=ctf-net -d --rm --name nginx_test nginx

  • После чего получаем адрес этого контейнера в docker сети: docker inspect nginx_test | grep IPAddress

В данном случае это 192.168.158.2 , так что на основной системе при подключенном VPN в браузере захожу на сайт 192.168.158.2

Сайт контейнера открыт
Сайт контейнера открыт

Тест сети от контейнера

Сделаем проверку в обратную сторону.

Получаю свой адрес через команду ifconfig.
Получаю свой адрес через команду ifconfig.
  • Получаю свой адрес через команду ifconfig: docker exec -ti nginx_test /bin/bash

  • После чего получаю консоль внутри контейнера от пользователя root.

     По стандарту в базовом контейнере nginx нет ping, поэтому его необходимо поставить: apt update && apt install iputils-ping

  • После чего проверяем доступ клиентов к VPN:

Проверка доступа клиентов к VPN
Проверка доступа клиентов к VPN

Все работает, так что выходим из контейнера командой exit

Сохранить правила iptables

Теперь нужно сохранить правила iptables.

  • Переключаемся на пользователя root на сервере: sudo su -

  • И сохраняем: iptables-save > /etc/iptables/rules.v4

Запуск CTF-заданий

Тут все просто: любой контейнер, запущенный с параметром: --network=ctf-net, сразу будет настроен и работать с нужной нам сети.

Так что, запуск заданий будет выглядеть вот так: docker run --network=ctf-net -d --rm --name CONTAINER_NAME IMAGES_NAME

Настройка доступа до изолированной сети виртуальных машин

Docker – это отлично, но также задачи могут быть развёрнуты на виртуальных машинах, так что их тоже необходимо подключить.

  • Для этого в нашем гипервизоре создадим виртуальную сеть.

  • Затем подключим нашу машину новым сетевым интерфейсом в эту сеть.

После настроек наши интерфейсы выглядят таким образом
После настроек наши интерфейсы выглядят таким образом

Здесь мы можем видеть, что ens19 остался без адреса, так как DHCP сервера в изолированной сети нет. Следовательно, нужно прописать статистический адрес на интерфейс и поставить DHCP сервер.

  • Уже известным образом прописываем адрес на интерфейс (пункт Статистический адрес в сети)

Должен получиться такой конфиг
Должен получиться такой конфиг
После перезагрузки
После перезагрузки

Установка DHCP (Этот модуль можно свернуть)

sudo apt install isc-dhcp-server

После установки мы получим ошибку, что служба не смогла запуститься.
После установки мы получим ошибку, что служба не смогла запуститься.
  • Редактируем конфиг: sudo nano /etc/dhcp/dhcpd.conf

  • Добавляем вот это в пустое место:

    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;

    }

Результат должен выглядеть так
Результат должен выглядеть так

***Хотелось бы обратить внимание на range 10.0.50.10 10.0.50.240 .

Тут устанавливается диапазон раздачи IP адресов DHCP сервером. То есть, 2-10 и 240- 254 можно использовать в качестве сервисных адресов. Например,службы мониторинга или сервисы с обязательно статическим адресом.

  • Редактируем следующий файл: sudo nano /etc/default/isc-dhcp-server

  • Нужно указать интерфейс, на который будет раздавать адреса DHCP сервер.

Пример
Пример
  • Перезагружаем службу: sudo systemctl restart isc-dhcp-server

  • Проверяем ее статус: sudo systemctl status isc-dhcp-server

Запуск службы
Запуск службы

Теперь стоит проверить ее в деле.

  • Для этого создадим виртуальную машину и подключим ее в той же сети. Машина должна получить IP адрес.

Машина получила адрес, но 8.8.8.8 не пингует
Машина получила адрес, но 8.8.8.8 не пингует

Правила iptables

Добавляем маршрут в конфиг OpenVPN, который скачали с сервера.

Теперь он выглядит так
Теперь он выглядит так
  • Прописываем правила для iptables. (Выполнять от root)

    Обратите внимание на название интерфейсов. Вероятно, они будут называться по-другому.

    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

  • После чего пробуем с виртуальной машины пинговать клиента, подключенного к VPN:

Пингуем клиента
Пингуем клиента
С клиента пингуем виртуальную машину
С клиента пингуем виртуальную машину
  • Сохраняем новые правила: iptables-save > /etc/iptables/rules.v4

    На этом данный этап настройки для виртуальных машин можно считать оконченным.

Дополнительные правила iptables

  1. Обратите внимания на названия интерфейсов. Вероятно, они будут называться по-другому.

  2. Желательно сделать бекап виртуальной машины, чтобы ничего случайно не сломать.

  • Разрешим подключения по ssh: sudo iptables -A INPUT -i ens18 -s 192.168.0.1/24 -p tcp --dport 22 -j ACCEPT

  • Откроем доступ до udp порта VPN: sudo iptables -A INPUT -i ens18 -p udp --dport 1194 -j ACCEPT

  • Применим политику по умолчанию: sudo iptables -P FORWARD DROP

  • Запрет на выход в интернет для tun:

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

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

Запрет на выход в интернет для Docker

  • Контейнер по стандарту имеет возможность ходить в интернет, но нас это не устраивает, поэтому нужно добавить правило запрещающие это:

    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

Все работает
Все работает

Запрет на выход в интернет для VM

Так как у нас нет правил разрешающих движение трафика в интернет, то виртуальная машина по стандарту не сможет выйти в интернет.

Разрешение на выход в интернет для Docker

Чтобы разрешить выход в интернет, нужно снять правило.

  • Это можно сделать вот так: iptables -D FORWARD -s 192.168.158.0/24 -j DROP

  • Вернуть правило обратно и заблокировать доступ к интернету:

    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

Разрешение на выход в интернет для VM

Здесь немного сложнее. Нужно активировать пересылку пакетов, сделать это можно используя MASQUERADE. А также разрешить пересылку пакетов для адресов 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

Обращаю внимание,что ens18 – название интерфейса, который смотрит в интернет.

Также обратная задача, а именно – вернуть запрет на трафик:

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

Сохранение правил

 После нужных нам изменений сохранили правила: iptables-save > /etc/iptables/rules.v4

Так как при перезагрузке правила, которые не были сохранены, автоматически будут удалены, то это очень удобно использовать.

На данном стенде контейнеры и виртуальные машины по стандарту не имеют выхода в интернет, но если мне нужно что-то скачать для настройки, то я просто прописываю правило для этого, а после работы выключаю стенд.

В итоге, когда команда приходит атаковать стенд, интернета на задачах по стандарту нет, и не нужно помнить, было разрешение на вход или нет.

Вывод и немного мыслей

Стенд для проведения CTF-соревнований или проведения экспериментов собрать не составит особого труда.

Изначально проект делался для проведения соревнований в университете, а в итоге получился действительно удобным, и было развернуто несколько копий под разные задачи.

(Одна стоит даже дома, для локальных экспериментов с друзьями. Так как к такой сети можно подключиться удаленно и иметь свой стенд для тренировок.)

Как было сказано ранее, проводились соревнования в институте, так что очень хотелось следить за нагрузкой в реальном времени. Соревнования полностью проходили на Docker-контейнерах, а для отслеживания их ресурсов прекрасно подходит проект domolo.

Просто скачиваем и запускаем через docker-compose. В итоге имеем статистику по ресурсам у каждого контейнера. Так что, если кто-то запустит условный майнер в контейнере, то это сразу будет видно, и этот контейнер можно будет быстро уничтожить.

Комментарии (2)


  1. razoryoutub
    17.07.2024 15:43

    Как альтернативу можно было бы рассмотреть Wireguard, однако:

    Запуск его конфигов несколько сложнее для пользователя.

    Извините, а можно пример? Вроде и там, и там по одному клику нужно сделать...

    Интересная статья. Помню был случай на ctf - использовал свой ноутбук как промежуточный сервер для других участков команды: был подключен к ctf через openvpn, а другие участники ко мне через wireguard. Не помню уже почему так было сделано, но на тот момент самый простой и главное рабочий вариант оказался


  1. winwood
    17.07.2024 15:43

    Для CTF гораздо удобней в OpenVPN вместо сертификатов использовать пароли. Нет этой волокиты сперва с генерацией сертификатов, а потом с их отзывом после окончания CTF - логично же использовать созданную инфраструктуру повторно, но вот людей со старыми ключами пускать не хотелось бы.
    Ну и второй момент - это фиксированные адреса для участников. Так проще разбирать логи, в случае чего.

    А еще помнится, перенаправляли все адреса, не используемые сервисами, на одну виртуалку. Чтобы nmap -sP выдавал полный диапазон и невозможно было определить, где же висит сервис, пока условие задачи не будет доступно. По этой же причине не использовались стандартные порты для сервисов.