Однажды была поставлена задача создать собственный стенд для проведения CTF-соревнований. Задачи были подготовлены в формате Docker-контейнеров, но для них была необходима сеть.
Возникает логичный вопрос «Зачем?». Сеть позволит выделить отдельный IP адрес каждой задаче. В принципе, конечно, можно обойтись и без сети, однако такой подход имеет несколько минусов, а именно:
Службы имеют нестандартные порты.
Все задачи развернуты на одном адресе, так что из задания уходит важная задача по сканированию портов. Участники сразу будут их знать, и останется только уточнить службу.
Раздача портов вручную. В моих задачах по большей части был не один сервис на задачу. Так что возникает необходимость логического разделения заданий. А также могут быть проблемы с автоматическим запуском задач.
Задачи подразумевают использование reverse shell, а если сервис разворачивать в интернете, то пользователи не смогут его использовать без белого адреса. Получается, что 99% пользователей не смогут решить задачу без использования, например, VPS.
Хотелось бы ограничить доступ в интернет задачам. Чтобы после проведения успешной атаки пользователь не смог использовать ресурсы машины в своих целях.
Кратко о том, что хотелось получить:
Задачи должны быть в изолированной сети, без доступа в интернет, но при необходимости должна быть возможность его выдать.
Каждый участник должен иметь индивидуальный IP адрес.
Каждая задача должна иметь индивидуальный IP адрес.
Не должно быть ограничений на использование портов у каждой задачи.
После решения задачи пользователь не должен использовать ресурсы в своих целях.
-
Один конфиг VPN для всех участников соревнования.
В статье рассматривается возможность работы как с Docker-контейнерами, так и виртуальными машинами. Но, если какой-то пункт не нужен, его можно пропустить.
Софт и железо
В качестве гипервизора использовался Proxmox 8.1.4, а в качестве маршрутизатора — виртуальная машина Debian 12.
Для понимания структуры проекта ниже представлена схема сети:
![Если в вашей сети используются эти диапазоны адресов, то при создании подобной инфраструктуры, имеет смысл их заменить, так как можно столкнуться с проблемой. Если в вашей сети используются эти диапазоны адресов, то при создании подобной инфраструктуры, имеет смысл их заменить, так как можно столкнуться с проблемой.](https://habrastorage.org/getpro/habr/upload_files/41d/706/782/41d706782f353fa4be75538400070c3d.jpg)
Может возникнуть вопрос: «Почему именно 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»](https://habrastorage.org/getpro/habr/upload_files/dcf/f06/e2f/dcff06e2f9b7933b8bafdcf09fcabbc6.png)
IP Forward
Далее надо включить IP forward в системе:
sudo nano /etc/sysctl.conf
Редактируем параметр: net.ipv4.ip_forward = 1.
Нужно поставить значение 1
Статический адрес в сети
Так как наше устройство будет выполнять роль сервера и маршрутизатора, то получать адрес по DHCP будет не самым правильным решением. Следовательно, необходимо прописать статический адрес:
![Статический адрес Статический адрес](https://habrastorage.org/getpro/habr/upload_files/26d/662/cff/26d662cff7790c3a92610c38ce656372.png)
Редактируем файл /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
![Пример конфига Пример конфига](https://habrastorage.org/getpro/habr/upload_files/f12/5a1/665/f125a16650312e94da77c713e520b0d6.png)
После перезагрузки сервер получит адрес 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"
>> ~/.bashrcecho "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_АДРЕС_СЕРВЕРА
![Например так Например так](https://habrastorage.org/getpro/habr/upload_files/a09/da0/4b9/a09da04b92a97b02e184738745d416e7.png)
Генерируем ключи:
ovpn_initpki
![В этот момент нужно придумать и ввести ключ В этот момент нужно придумать и ввести ключ](https://habrastorage.org/getpro/habr/upload_files/68d/ae1/9bf/68dae19bf68d2133280379192d380eed.png)
-
Пример ключа:
Qwertyqwertyqwerty123
Далее следуем текстовым инструкциям:
Генерируем конфиги для участников. При создании будет запрошен ключ, который мы вводили выше:
easyrsa build-client-full ctf-team nopass
-
Сохраняем конфиг в файл:
ovpn_getclient ctf-team > ./ctf-team.ovpn
Так как нам нужно, чтобы участники могли подключаться через один конфиг к сети, отредактируем конфиг:
vim /etc/openvpn/openvpn.conf
Добавляем строку:
duplicate-cn
![Пример Пример](https://habrastorage.org/getpro/habr/upload_files/cb7/b5f/408/cb7b5f40812dc1e9e7f1caeccd4aaac5.png)
Запускаем OpenVPN:
ovpn_run
-
Скачиваем конфиг сервера по пути
/root/ctf-team.ovpn
к себе на компьютер.Это может выглядеть так:
scp root@SERVER_IP:/root/ctf-team.ovpn ~
И пробуем запустить.
Запускам на основной системе:
sudo openvpn ctf-team.ovpn
![Проверяем, чтобы у нас появился интерфейс tun Проверяем, чтобы у нас появился интерфейс tun](https://habrastorage.org/getpro/habr/upload_files/de5/e61/2b2/de5e612b27b619b2e100fbd80bc4f066.png)
Если все работает, то пробуем перезапустить сервер и проверяем снова. 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
![Пример Пример](https://habrastorage.org/getpro/habr/upload_files/65c/c38/362/65cc38362971c3b6b66d526be03f84d5.jpg)
После чего подключаемся к 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
![Сайт контейнера открыт Сайт контейнера открыт](https://habrastorage.org/getpro/habr/upload_files/83b/dcb/3c4/83bdcb3c49b196f65203bae87848bf31.jpg)
Тест сети от контейнера
Сделаем проверку в обратную сторону.
![Получаю свой адрес через команду ifconfig. Получаю свой адрес через команду ifconfig.](https://habrastorage.org/getpro/habr/upload_files/c55/bf9/786/c55bf97860088ac1d116007951f7c1de.png)
Получаю свой адрес через команду ifconfig:
docker exec -ti nginx_test /bin/bash
-
После чего получаю консоль внутри контейнера от пользователя root.
По стандарту в базовом контейнере nginx нет ping, поэтому его необходимо поставить:
apt update && apt install iputils-ping
После чего проверяем доступ клиентов к VPN:
![Проверка доступа клиентов к VPN Проверка доступа клиентов к VPN](https://habrastorage.org/getpro/habr/upload_files/3a8/ef8/57a/3a8ef857a096ddb24d88e4ec8a2e800d.png)
Все работает, так что выходим из контейнера командой 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 – это отлично, но также задачи могут быть развёрнуты на виртуальных машинах, так что их тоже необходимо подключить.
Для этого в нашем гипервизоре создадим виртуальную сеть.
Затем подключим нашу машину новым сетевым интерфейсом в эту сеть.
![После настроек наши интерфейсы выглядят таким образом После настроек наши интерфейсы выглядят таким образом](https://habrastorage.org/getpro/habr/upload_files/6aa/f44/dfb/6aaf44dfb28f3ff82ad63c133c0f2732.png)
Здесь мы можем видеть, что ens19 остался без адреса, так как DHCP сервера в изолированной сети нет. Следовательно, нужно прописать статистический адрес на интерфейс и поставить DHCP сервер.
Уже известным образом прописываем адрес на интерфейс (пункт
Статистический адрес в сети
)
![Должен получиться такой конфиг Должен получиться такой конфиг](https://habrastorage.org/getpro/habr/upload_files/a6e/048/7c8/a6e0487c8b0801897786b8084897835e.png)
![После перезагрузки После перезагрузки](https://habrastorage.org/getpro/habr/upload_files/095/f7d/209/095f7d209ffe9a3c43b6721664239a56.png)
Установка DHCP (Этот модуль можно свернуть)
sudo apt install isc-dhcp-server
![После установки мы получим ошибку, что служба не смогла запуститься. После установки мы получим ошибку, что служба не смогла запуститься.](https://habrastorage.org/getpro/habr/upload_files/aa9/b37/559/aa9b375596e9ad43930ad9f4daeeaf7a.png)
Редактируем конфиг:
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;
}
![Результат должен выглядеть так Результат должен выглядеть так](https://habrastorage.org/getpro/habr/upload_files/ae2/af4/de1/ae2af4de181904c126d12d28e9dd85cb.png)
***Хотелось бы обратить внимание на range 10.0.50.10 10.0.50.240 .
Тут устанавливается диапазон раздачи IP адресов DHCP сервером. То есть, 2-10 и 240- 254 можно использовать в качестве сервисных адресов. Например,службы мониторинга или сервисы с обязательно статическим адресом.
Редактируем следующий файл:
sudo nano /etc/default/isc-dhcp-server
Нужно указать интерфейс, на который будет раздавать адреса DHCP сервер.
![Пример Пример](https://habrastorage.org/getpro/habr/upload_files/ad8/40d/84b/ad840d84b614a84c02c66c57025acee4.png)
Перезагружаем службу:
sudo systemctl restart isc-dhcp-server
Проверяем ее статус:
sudo systemctl status isc-dhcp-server
![Запуск службы Запуск службы](https://habrastorage.org/getpro/habr/upload_files/566/44e/295/56644e2951a5736be16905e02cb1b2b7.png)
Теперь стоит проверить ее в деле.
Для этого создадим виртуальную машину и подключим ее в той же сети. Машина должна получить IP адрес.
![Машина получила адрес, но 8.8.8.8 не пингует Машина получила адрес, но 8.8.8.8 не пингует](https://habrastorage.org/getpro/habr/upload_files/974/472/ebc/974472ebc7d285f8850e3ecd07f80356.png)
Правила iptables
Добавляем маршрут в конфиг OpenVPN, который скачали с сервера.
![Теперь он выглядит так Теперь он выглядит так](https://habrastorage.org/getpro/habr/upload_files/78a/879/375/78a87937578d8fa8d219b1770f988081.png)
-
Прописываем правила для 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:
![Пингуем клиента Пингуем клиента](https://habrastorage.org/getpro/habr/upload_files/2d1/621/3db/2d16213db7c65ecaca20774478cebeaa.png)
![С клиента пингуем виртуальную машину С клиента пингуем виртуальную машину](https://habrastorage.org/getpro/habr/upload_files/d5e/229/9f4/d5e2299f4412f887fc703e0866e05e86.png)
-
Сохраняем новые правила:
iptables-save > /etc/iptables/rules.v4
На этом данный этап настройки для виртуальных машин можно считать оконченным.
Дополнительные правила iptables
Обратите внимания на названия интерфейсов. Вероятно, они будут называться по-другому.
Желательно сделать бекап виртуальной машины, чтобы ничего случайно не сломать.
Разрешим подключения по 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
![](https://habrastorage.org/getpro/habr/upload_files/c5e/b14/844/c5eb148440b853a8d6358f0c7cb39c0a.png)
-
Контейнер по стандарту имеет возможность ходить в интернет, но нас это не устраивает, поэтому нужно добавить правило запрещающие это:
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
![Все работает Все работает](https://habrastorage.org/getpro/habr/upload_files/099/357/026/099357026f78787fe2f168f4fba243ad.png)
Запрет на выход в интернет для 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)
winwood
17.07.2024 15:43Для CTF гораздо удобней в OpenVPN вместо сертификатов использовать пароли. Нет этой волокиты сперва с генерацией сертификатов, а потом с их отзывом после окончания CTF - логично же использовать созданную инфраструктуру повторно, но вот людей со старыми ключами пускать не хотелось бы.
Ну и второй момент - это фиксированные адреса для участников. Так проще разбирать логи, в случае чего.А еще помнится, перенаправляли все адреса, не используемые сервисами, на одну виртуалку. Чтобы nmap -sP выдавал полный диапазон и невозможно было определить, где же висит сервис, пока условие задачи не будет доступно. По этой же причине не использовались стандартные порты для сервисов.
razoryoutub
Извините, а можно пример? Вроде и там, и там по одному клику нужно сделать...
Интересная статья. Помню был случай на ctf - использовал свой ноутбук как промежуточный сервер для других участков команды: был подключен к ctf через openvpn, а другие участники ко мне через wireguard. Не помню уже почему так было сделано, но на тот момент самый простой и главное рабочий вариант оказался