mirror of
https://github.com/mrvasil/ctf-ad-mirror-tool-rust.git
synced 2026-10-24 13:09:26 +00:00
139 lines
12 KiB
Markdown
139 lines
12 KiB
Markdown
# Опциональная подмена флагов
|
||
|
||
Режим **выключен по умолчанию**, отдельно включается у каждого сервиса в его
|
||
настройках. Без него прокси передаёт байты обычным путём. Переключатель влияет
|
||
только на новые TCP-соединения; открытые сохраняют свой режим до завершения.
|
||
Существующие TOML/state-файлы совместимы: отсутствующее `flag_protection` равно false.
|
||
|
||
## Как работает
|
||
|
||
Видимый входящий флаг заменяется случайным токеном **той же длины и формы**.
|
||
Повторный флаг внутри одного сервиса получает тот же токен до истечения срока
|
||
записи. Сервисы разделены по неизменяемому ID. Подмена выполняется один раз до
|
||
отправки primary и репликам. В ответах primary восстанавливаются только токены,
|
||
которые есть в кеше этого сервиса. Неизвестные совпадения передаются без изменения.
|
||
|
||
Это **токенизация**, а не самодельное шифрование с сохранением длины. Соответствия
|
||
зашифрованы XChaCha20-Poly1305 с независимым случайным 192-битным nonce на запись
|
||
(RustCrypto). SQLite хранит зашифрованные записи и служебные данные (схема, срок
|
||
истечения, число записей), но не открытые флаги/токены. Ключ — 256 бит из окружения.
|
||
В рабочей памяти соответствия доступны процессу; флаги и ключ не выдаются API,
|
||
не выводятся в панель/логи и не записываются в обычный topology state.
|
||
|
||
Новое соответствие подтверждается SQLite transaction (`synchronous=EXTRA`, включая синхронизацию каталога журнала) **до**
|
||
отправки токена в сеть. Повторное обращение не требует записи. Один фоновый worker
|
||
владеет БД и индексами; ограниченная очередь принимает только найденные совпадения.
|
||
Сетевые задачи асинхронно ждут результат без удержания сетевых блокировок. Если
|
||
клиент исчез после приёма задания worker, начатая запись всё равно завершается.
|
||
Это добавляет задержку при первом появлении уникального флага; режим не обещает
|
||
скорость обычной пересылки. Не включайте его без проверки конкретного протокола.
|
||
|
||
## Настройка
|
||
|
||
Создайте ключ **один раз** и храните его отдельно от резервной копии базы. Например,
|
||
в своей защищённой директории (не в Git):
|
||
|
||
```sh
|
||
umask 077
|
||
mkdir -p data
|
||
# Выполнять только для новой пустой базы; не перезаписывать существующий ключ.
|
||
( set -C; openssl rand -hex 32 > data/flag-key.hex )
|
||
export MIRROR_FLAG_KEY="$(cat data/flag-key.hex)"
|
||
```
|
||
|
||
Добавьте секцию в TOML:
|
||
|
||
```toml
|
||
[flags]
|
||
database = "data/flags.sqlite3"
|
||
key_env = "MIRROR_FLAG_KEY"
|
||
prefix = ""
|
||
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||
payload_length = 31
|
||
suffix = "="
|
||
ttl_seconds = 86400
|
||
max_entries = 10000
|
||
partial_timeout_ms = 5000
|
||
```
|
||
|
||
Этот пример распознаёт `[A-Z0-9]{31}=`. Формат задаётся как буквальный prefix,
|
||
фиксированное число символов из alphabet и буквальный suffix. Это не произвольный
|
||
regex. Например, для `FLAG{` + 32 hex-символа + `}` задайте prefix `FLAG{`, alphabet
|
||
`0123456789ABCDEF`, payload_length 32 и suffix `}`. Регистр важен. Совпадения ищутся
|
||
как подстроки, слева направо, без перекрытий; границы слов не подразумеваются.
|
||
|
||
`payload_length`: 16–128, alphabet: 16–94 уникальных печатных ASCII-символа;
|
||
prefix/suffix: до 32 печатных ASCII-символов. Ёмкость: 1–100000 записей,
|
||
TTL: 60–604800 секунд, тайм-аут фрагмента: 100–60000 мс.
|
||
Путь БД отсчитывается от каталога TOML. Файлы БД и её lock имеют права0600 на Unix;
|
||
одна БД может обслуживаться только одним процессом Mirror Tool.
|
||
|
||
После настройки перезапустите процесс с ключом в окружении. Затем кликните
|
||
название сервиса → **«Подмена флагов»** → «Сохранить». При отсутствии готового кеша
|
||
переключатель недоступен, показано объяснение. Включённый режим обозначен замком
|
||
рядом с названием; сводные счётчики кеша доступны в справке ⓘ.
|
||
|
||
Для стартовой топологии можно указать `flag_protection = true` в нужном
|
||
`[[services]]`, но существующий `state.json` имеет приоритет над стартовой
|
||
топологией TOML. Обычный CLI `check` проверяет структуру конфигурации, но не
|
||
открывает БД и не проверяет ключ; проверка ключа выполняется при запуске.
|
||
|
||
## Срок хранения и сбои
|
||
|
||
- TTL отсчитывается от первого сохранения; повторный флаг не продлевает его.
|
||
Настройте его **дольше срока жизни флага плюс задержка хранения/выдачи ответа**.
|
||
- Истёкшие записи удаляются при следующем запросе к кешу либо запуске. Счётчик
|
||
entries отражает хранимые записи до этой очистки. После истечения токен неизвестен
|
||
и не восстанавливается. Изменение TTL относится к новым записям.
|
||
- Нет вытеснения живых записей ради новых. При переполнении, ошибке записи или
|
||
недоступности worker защищённая сессия завершается с ошибкой; исходный флаг
|
||
не отправляется вместо токена. Уже отправленная часть другого содержимого
|
||
не откатывается. Обычные сервисы продолжают работать независимо.
|
||
- Неверный ключ, изменение формата для существующей БД, повреждённые записи либо
|
||
превышение новой ёмкости запрещают запуск вместо молчаливого сброса кеша.
|
||
- Для восстановления нужны **и база, и прежний ключ**. Ротация ключа/формата с
|
||
миграцией живых записей не реализована. Не удаляйте БД или ключ во время игры.
|
||
Резервную копию SQLite делайте при остановленном процессе или средствами SQLite.
|
||
- Аутентификация записей обнаруживает повреждение/неверный ключ, но не защищает
|
||
от удаления всей базы или подстановки её старой копии локальным злоумышленником.
|
||
|
||
## TCP и ограничения протокола
|
||
|
||
Обработчик распознаёт флаги через любые границы TCP-чтений. Удерживается только
|
||
возможное начало незаконченного совпадения — максимум длина флага минус один байт.
|
||
Остальные байты передаются дальше. При EOF незавершённый фрагмент уже не может стать
|
||
флагом и отправляется как обычный текст. Если продолжения/EOF нет до тайм-аута,
|
||
соединение закрывается: выпуск такого фрагмента по таймеру мог бы пропустить флаг.
|
||
|
||
TCP не содержит границ сообщений. Если запрос заканчивается возможным началом
|
||
флага и приложение ждёт ответа, может потребоваться другой формат или адаптер
|
||
протокола; универсально разрешить такую неоднозначность нельзя. Пустые prefix/suffix
|
||
повышают вероятность случайных совпадений. Используйте точный формат игры.
|
||
|
||
Режим работает только с флагами, видимыми в исходных байтах. Нет декодирования
|
||
TLS, gzip, URL/base64/JSON-escape и прочих представлений. Сохранение длины оставляет
|
||
корректными длины простых сообщений, но не подписи, checksums или семантику
|
||
аутентификации. Побочные эффекты зеркалируемых серверов остаются отдельной проблемой.
|
||
Подмена уже хранящихся данных задним числом не выполняется.
|
||
|
||
**Это не фильтр атакующих:** известные токены восстанавливаются всем клиентам.
|
||
Кража через уязвимый сервис, доступный через этот же прокси, может вернуть оригинал.
|
||
Режим скрывает оригиналы от upstream, но не заменяет исправление уязвимости.
|
||
|
||
## Разработка
|
||
|
||
`src/flags/mod.rs`: настройки/формат; `stream.rs`: обработка TCP-фрагментов;
|
||
`vault.rs`: криптография, SQLite, actor, TTL и статистика. Сетевой engine держит
|
||
настройку в снимке маршрута на время сессии. Проверки находятся в
|
||
`tests/flag_protection.rs` и unit-тесте expiry внутри vault.
|
||
|
||
Изолированный UI fixture с новым тестовым ключом и **выключенной подменой**:
|
||
|
||
```sh
|
||
python3 scripts/ui_fixture.py target/release/mirror-tool \
|
||
--data-dir test-results/flags-ui --port-offset 200 --flag-vault
|
||
```
|
||
|
||
Fixture создаёт локальный файл ключа0600; повторно использует его после рестарта.
|
||
Он предназначен только для локального демо, не для настоящей игры.
|