Поддерживаются версии, которые активно поддерживаются в ветке main и для которых проходят проверки CI.
Если вы нашли уязвимость:
- Не публикуйте детали публично (не создавайте публичный issue с PoC).
- Используйте приватный отчёт в GitHub Security Advisories для репозитория.
- Укажите:
- минимальный путь воспроизведения,
- влияние на безопасность,
- уровень риска и возможные косвенные последствия,
- шаги для временного обхода (если есть).
Если механизм private advisories недоступен, отправьте письмо на контакт проекта с пометкой [SECURITY].
- Команда проверяет репорт в приоритетном режиме.
- При подтверждении создаётся исправление и при необходимости закрытый advisory.
- После релиза уязвимости публикуется заметка в примечаниях релиза.
- Не храните чувствительные ключи, токены и пароли в
config.json. - Для локальных и рабочих окружений рекомендуется использовать переменные окружения для секретов.
- В CI и скриптах не выводите содержимое секретов в лог.
Severity: MEDIUM
Файл: crates/openmines-server/src/net/session/auth/gui_flow.rs
GUI-логин отправляет пароль как текст внутри TY-пакета (passwd:СЕКРЕТ).
TCP-соединение без TLS — пароль виден в дампе трафика вербатим.
Для фикса нужен tokio-rustls на сервере и соответствующие изменения в Unity-клиенте.
Требует решения о поддержке шифрования в клиенте.
Смягчение на сервере: пароли хранятся хешированными (SHA-256 + per-user salt), поэтому перехват не даёт доступа к другим сервисам — но позволяет replay-вход до смены пароля.
Файл: crates/openmines-storage/src/clans.rs — accept_clan_request
Суть: Офицер мог добавить любого игрока в клан без его согласия crafted GUI_-пакетом.
UPDATE в БД выполнялся без проверки наличия заявки.
Фикс: UPDATE теперь условный через EXISTS (SELECT ... FROM clan_requests).
Файл: crates/openmines-storage/src/clans.rs — set_clan_rank
Суть: Owner мог выдать ранг Officer любому игроку в любом другом клане.
Фикс: SQL добавлен фильтр AND clan_id = ?.
Файл: crates/openmines-server/src/net/session/social/clans.rs — handle_clan_kick
Суть: target_rank при кике defaulted в ClanRank::None (0) если target не в клане
актора. Проверка player_rank <= target_rank всегда false для любого реального члена клана
(Member=10, Officer=50, Leader=100) → авторизация обходилась. Любой член клана мог
де-кланировать любого игрока в игре через clan_kick_id:{arbitrary_id} или /clan kick name.
Фикс: Добавлен явный guard: если target не в members-списке актора → reject.
Файл: crates/openmines-server/src/net/session/ui/gui_buttons.rs — handle_market_buy
Суть: cost = to_buy * buy_price не защищено от переполнения. В release-сборке
Rust использует wrapping-арифметику: при to_buy ≈ i64::MAX / buy_price + 1 произведение
оборачивается в отрицательное число. Проверка money < cost не срабатывает (любые деньги ≥
отрицательного cost), и money -= negative_cost добавляет деньги вместо их списания.
Параллельно кристаллы [i] += to_buy тоже могли переполняться.
Эксплойт: buy:9223372036854775807:0:0:0:0:0 через TY-пакет → мгновенное обогащение.
Фикс: checked_mul прерывает итерацию при переполнении; saturating_add для кристаллов.
Файлы: crates/openmines-storage/src/players.rs, crates/openmines-server/src/net/session/auth/gui_flow.rs
Суть: Пароли записывались в SQLite сырым текстом, сравнивались прямым ==.
Фикс: Новые пароли: SHA-256(user_hash + ":" + passwd). Старые plaintext мигрируются
автоматически при следующем логине игрока.
Файл: crates/openmines-server/src/net/session/ui/gui_buttons.rs — handle_storage_transfer
Severity: CRITICAL
Суть: Два члена клана с синхронными пакетами могли задублировать кристаллы из
общего Storage. Функция читала кристаллы (ECS read-lock, отпускала), валидировала,
затем писала (два отдельных write-lock). Оба игрока читали storage=[100,...], оба
вычисляли new_player=[100,...], оба записывали → 100 кристаллов превращались в 200.
Фикс: Единый ecs.write() lock через modify_pack_with_db покрывает
чтение, валидацию и запись обоих — и storage, и player — атомарно.
Файл: crates/openmines-server/src/net/session/ui/auction_gui.rs — place_bet
Severity: HIGH
Суть: Два игрока одновременно ставили на лот с существующим покупателем.
Оба читали одинаковый buyer_id из БД, оба вызывали credit_money(old_buyer, cost) →
предыдущий покупатель получал двойной рефанд (деньги из воздуха).
Фикс: CAS-паттерн: UPDATE orders ... WHERE buyer_id=? AND cost=? — атомарен
на уровне SQLite (serialized writes). Только один запрос получает rows_affected>0
и вызывает credit_money; проигравший просто обновляет GUI.
Файл: crates/openmines-server/src/net/session/ui/auction_gui.rs — place_bet
Severity: HIGH
Суть: Проверка bidder_money >= o.cost (старая цена) вместо >= amount
(новая ставка). При o.cost=100, required=101, bidder_money=100 проверка
проходила, а деньги списывались: 100 - 101 = -1 → отрицательный баланс.
Фикс: Условие изменено на bidder_money >= amount.
Файл: crates/openmines-server/src/net/session/ui/auction_gui.rs — create_order
Severity: MEDIUM
Суть: Предметы списывались из инвентаря до вызова db.create_order. Если
INSERT в БД падал — предметы терялись безвозвратно (только tracing::error!).
Фикс: При ошибке БД — возврат предметов обратно в инвентарь + re-sync клиенту.
Файл: crates/openmines-server/src/net/session/play/dig_build.rs — box pickup
Severity: LOW
Суть: crystals[i] += bc[i] без overflow-защиты. Теоретически (очень большой
box) мог переполнить i64 и обнулить/инвертировать счётчик кристаллов.
Фикс: saturating_add — переполнение останавливается на i64::MAX.