Как стать автором
Обновить

Разворачивание инфраструктуры OpenCTF для AppSecFest 2025

Уровень сложностиПростой
Время на прочтение7 мин
Количество просмотров189

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

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

Нами было сделано Play Zone, где участвуя в интерактивных играх и квизах связанных с кибербезопасностью вы могли заработать коины для покупки эксклюзивного мерча.
А также мы решили простроить целую инфраструктуру для данного мероприятия и проведения локального соревнования, где приняли участие как и уже бывалые ИБ-шники, так и совсем зеленные новички.

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

План статьи:

1) Архитектура проекта и немного воды
2) Настройка OpenVPN
3) Телеграм ботыы
4) CTFd
5) Wazuh
6) Zabbix

Архитектура проекта:

Основной идеей у нас было создать безопасную систему, в который участники могли бы комфортно решать задания и мы видели если бы они совали носы куда не стоит)

И стоит заметить, что тут скорее не про то как НУЖНО делать, а про то как видели и хотели сделать мы.

OpenVPN 

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

Было поднято 2 Openvpn

1) Для обычных юзеров без авторизации  - доступ к веб таскам и CTFd 

2) Для админов с авторизацей - доступ к SIEM(Wazuh), Zabbix, веб таскам и CTFd по ssh 

sudo apt update sudo apt install openvpn easy-rsa make-cadir ~/openvpn-ca cd ~/openvpn-ca ./easyrsa init-pki ./easyrsa build-ca # задайте имя и пароль CA ./easyrsa gen-req server_non_auth nopass ./easyrsa sign-req server server_non_auth ./easyrsa gen-req server_auth nopass ./easyrsa sign-req server server_auth ./easyrsa gen-dh openvpn --genkey secret ta.key

Затем настройка конфигов

  1. для юзеров:

nano /etc/openvpn/server/server_non_auth.conf port 1194 proto udp dev tun topology subnet ; Пути к сертификатам и ключам ca /etc/openvpn/ca.crt cert /etc/openvpn/server_non_auth.crt key /etc/openvpn/server_non_auth.key dh /etc/openvpn/dh.pem ; tls-auth /etc/openvpn/ta.key 0 # Разрешаем запуск скриптов script-security 3 # Подключаем скрипт, который будет вызываться при каждом входящем подключении client-connect /etc/openvpn/scripts/client-connect.sh ; Задаём основной пул IP (например, 10.10.10.0/24) server 10.10.10.0 255.255.255.0 ifconfig-pool-persist /etc/openvpn/ipp_non_auth.txt ; Раздача маршрута для доступа к сети 192.168.52.0/24 push "route 192.168.52.0 255.255.255.0" keepalive 10 120 cipher AES-256-CBC persist-key persist-tun status /var/log/openvpn_non_auth_status.log verb 3

client-connect /etc/openvpn/scripts/client-connect.sh - данная часть кода нужна для того чтобы выполнялся скрипт client-connect.sh при каждом подключении клиента #!/bin/bash # /etc/openvpn/scripts/client-connect.sh LOGFILE="/var/log/openvpn-client-connect.json" # Получаем метку времени timestamp=$(date '+%Y-%m-%dT%H:%M:%S%z') # Формируем JSON-строку json="{\"timestamp\":\"${timestamp}\",\ \"event\":\"client-connect\",\ \"common_name\":\"${common_name}\",\ \"untrusted_ip\":\"${untrusted_ip}\",\ \"untrusted_port\":\"${untrusted_port}\",\ \"vpn_ip\":\"${ifconfig_pool_remote_ip}\"}" echo "${json}" >> "${LOGFILE}" exit 0

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

В итоге получаем логи в формате json для удобной передачи в Wazuh

[root@vpn-rocky openvpn]# cat /var/log/openvpn-client-connect.json 

{"timestamp":"2025-04-22T19:21:21+0000","event":"client-connect","common_name":"client1","untrusted_ip":"176.64.21.98","untrusted_port":"27086","vpn_ip":"10.10.10.4"}

{"timestamp":"2025-04-23T00:25:10+0500","event":"client-connect","common_name":"client1","untrusted_ip":"176.64.21.98","untrusted_port":"29515","vpn_ip":"10.10.10.4"}

  1. для админов

Такая же конфигурации но только выдача админам IP из подсети 10.69.69.0/24  для доступа к админской подсети 192.168.69.0/24

Также добавили авторизацию. Авторизация происходить через проверку логина/пароля через PAM.

Далее идет настройка сети для того чтобы юзеры могли получить доступ к внутренней сети с помощью ВПН.

Телеграм боты

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

Автоматизировать мы решили следующие процессы:

1. Выдача доступов.
2. Поддержка
3. Озвучка First Blood'ов

  1. Бот для раздачи данных, кредов и openvpn файлов для доступа к CTFd и таскам

Сделать так чтобы одной командой участник мог получить все доступы, было легко.
Однако, надо было сделать так, чтобы этот же пользователь не мог получить доступы для еще одной учетки, для этого мы решили чтобы бот собирал user_id и приписывал их для каждой пары логин, пароль+vpn конфиг.

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

Пример работы:


2. Support bot

Обработка запросов участников это одна из неотъемлемых частей любой СТФки, поэтому тут я решил просто облегчить работу админам, человек создавал запросы через телеграм:

А мы обрабатывали их в отдельном мессенджере:

3. First Blood

Вот тут было сложно придумать как реализовать автоматическую озвучку всех First Blood-ов
Так как у нас был отдельный VPN, я решил со своего ноута подключить SSH тунель до сервера с CTFd.
Таким образом, чтобы при обращении на сервере CTFd на localhost:6000 этот запрос обращался на localhost:5000 моего ноута.

Команда для подключения(с моего ноута):
ssh -4 -N -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -R 127.0.0.1:6000:127.0.0.1:5000 user@{ctfd_ip}

Как происходит озвучка First Blood:

https://drive.google.com/drive/folders/1qaVZQR3AvCD5d4CztWlri06cCkHjBixr?usp=sharing


CTFd

Разворачивание CTFd было стандартное с контейнера, но решили сделать свой дизайн под эту сореву.


SIEM Wazuh

В качестве SIEM системы для сбора и мониторинга логов мы использовали Wazuh.

Для сбора логов и событии использовались Wazuh-agent, Packetbeat (для сбора и отправки в Logstash на сервере с Wazuh сетевого трафика), Filebeat (для сбора и отправки в Logstash на сервере с Wazuh кастомных логов), Logstash (для принятие логов и отправки в Wazuh).

Устанавливаем packetbeat на хост с VPN для мониторинга сетевого трафика и настраиваем:

[rockylinux@vpn-rocky packetbeat]$ sudo cat packetbeat.yml packetbeat.interfaces.device: any packetbeat.interfaces.poll_default_route: 1m packetbeat.interfaces.internal_networks: - private packetbeat.flows: timeout: 30s period: 10s packetbeat.protocols: - type: icmp enabled: true - type: http include_body_for: ["application/json", "text/html", "application/xml"] ports: [80, 8080, 8000, 5000, 8002,8088] send_headers: true send_all_headers: true send_request: true send_response: true - type: tls ports: - 443 - 993 - 995 - 5223 - 8443 - 8883 - 9243 - type: dns ports: [53] - type: mysql ports: [3306, 3307] - type: pgsql ports: [5432] - type: redis ports: [6379] - type: mongodb ports: [27017] - type: memcache ports: [11211] - type: thrift ports: [9090] - type: nfs ports: [2049] - type: cassandra ports: [9042] - type: amqp ports: [5672] - type: dhcpv4 ports: [67, 68] - type: sip ports: [5060] setup.template.settings: index.number_of_shards: 1 processors: - drop_event.when.not.or: - network.source.ip: "10.69.69.0/24" - network.source.ip: "10.10.10.0/24" - drop_fields: fields: - host.hostname - host.name - host.architecture - host.os.* - host.containerized - cloud - ecs - agent - docker - container - related - tags - input - service - event.dataset - event.kind - user_agent.device - url.query - status - status_phrase - http.request.headers - http.response.headers output.logstash: hosts: ["192.168.69.140:5000"]

Устанавливаем filebeat и настраиваем для сбора кастомных логов о статусах и подключениях в OpenVPN:

[rockylinux@vpn-rocky filebeat]$ sudo cat filebeat.yml filebeat.inputs: - type: log enabled: true paths: - /var/log/openvpn.log fields: category: openvpn log_type: main - type: log enabled: true paths: - /var/log/openvpn_auth_status.log fields: category: openvpn log_type: auth - type: log enabled: true paths: - /var/log/openvpn_non_auth.log fields: category: openvpn log_type: non_auth - type: log enabled: true paths: - /var/log/openvpn-client-connect.log fields: category: openvpn log_type: connect - type: log enabled: true paths: - /var/log/openvpn_non_auth_status.log fields: category: openvpn log_type: non_auth_status - type: log enabled: true paths: - /var/log/openvpn-client-connect.json json.keys_under_root: true json.add_error_key: true fields: category: openvpn log_type: client_connect output.logstash: hosts: ["192.168.69.140:5000"]

Теперь устанавливаем Logstash и настраиваем его на принятиях этих логов и отправке к Wazuh:

root@wazuh-zabbix:/etc/logstash/conf.d# cat wazuh-opensearch.conf input { beats { port => 5000 ssl => false } } filter { if [fields][log_type] == "json_connect" { json { source => "message" skip_on_invalid_json => true } date { match => ["timestamp", "ISO8601"] target => "@timestamp" timezone => "UTC" } } if [@metadata][beat] == "filebeat" { mutate { remove_field => [ "@version", "[agent]", "ecs.version", "[host][name]", "[input][type]", "[log]", "tags" ] } } } output { if [@metadata][beat] == "filebeat" and [fields][category] == "openvpn" { opensearch { hosts => ["https://localhost:9200"] index => "openvpn-%{[fields][log_type]}-%{+YYYY.MM.dd}" user => "admin" password => "wazuhpassword" ssl => true ssl_certificate_verification => false } } else if [@metadata][beat] == "filebeat" { opensearch { hosts => ["https://localhost:9200"] index => "submissions" user => "admin" password => "wazuhpassword" ssl => true ssl_certificate_verification => false } } else if [@metadata][beat] == "packetbeat" { opensearch { hosts => ["https://localhost:9200"] index => "packetbeat-logs-%{+YYYY.MM.dd}" user => "admin" password => "R.z9oDh6j9paU5UYlYtH*MKQ+IoUMUWR" ssl => true ssl_certificate_verification => false } } }


Что еще делали на Wazuh:

  1. Создание кастомный правил и декодеров

  1. Создание кастомных индексов 

  1. Создание кастомных дэшбордов 



ZABBIX - Grafana

В Grafana мы наблюдали за сетевым трафиком

И за нагрузками на ЦП

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

Этот опыт может быть кому-то полезен для построения чего-то своего.

Теги:
Хабы:
0
Комментарии0

Публикации

Работа

Ближайшие события