12 KiB
Опциональная подмена флагов
Режим выключен по умолчанию, отдельно включается у каждого сервиса в его
настройках. Без него прокси передаёт байты обычным путём. Переключатель влияет
только на новые 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):
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:
[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 с новым тестовым ключом и выключенной подменой:
python3 scripts/ui_fixture.py target/release/mirror-tool \
--data-dir test-results/flags-ui --port-offset 200 --flag-vault
Fixture создаёт локальный файл ключа0600; повторно использует его после рестарта. Он предназначен только для локального демо, не для настоящей игры.