
Патч безопасности и proof-of-concept для onion-балансировщика нагрузки GoBalance, охватывающие восстановление мастер-ключа через blindedSign и принятие поддельного дескриптора, с регрессионными тестами.
Пакет рекомендаций для gitlab.com/n0tr1v/gobalance — ветка master, коммит bb1b0f3 ("fix crash").
Статус: КРИТИЧЕСКИЙ — два независимых пути полного захвата. Ветка patch1 в upstream не исправляет ни один из них.
Этот пакет содержит полный патч безопасности, сквозное proof-of-concept для обоих путей атаки и регрессионные тесты, подтверждающие, что исправления работают. Он сопровождает полный отчёт об анализе уязвимостей ("Laporan Analisis Keamanan GoBalance"), подготовленный в связи с недавними инцидентами захвата onion-доменов, затронувшими два форума. Пошаговое руководство по сборке и тестированию находится в USAGE.md.
| # | Уязвимость | Критичность | Воздействие | Статус |
|---|---|---|---|---|
| 1 | Утечка мастер-ключа идентичности через blindedSign (постоянный префикс nonce) | КРИТИЧЕСКАЯ | Полное восстановление onion-идентичности из одного публичного дескриптора | Исправлено |
| 2 | RegisterDescriptor принимает поддельные дескрипторы экземпляров (нет проверки подписи / привязки) | КРИТИЧЕСКАЯ | Перехват трафика любого фронтенда GoBalance | Исправлено |
| 3 | Детерминированный RNG с временным seed в pkg/brand | ВЫСОКАЯ (опасная ловушка) | Предсказуемый ключевой материал для всего, что его использует | Удалено |
| 4 | Перемешивание introduction-point использует math/rand | НИЗКАЯ | Слабая случайность в коде, смежном с протоколом | Заменено на crypto/rand |
Диспетчер blindedSign() в pkg/stem/descriptor/hidden_service.go передавал
identityKey.Seed() — сырой 32-байтовый скаляр a — в
BlindedSignWithTorKey(). Ключи формата Tor — это расширенные ключи: 64 байта
(a || h), где h — это ключ PRF, из которого выводится префикс nonce для каждой подписи. При отсутствии h вход для вывода nonce был пустым, и
kPrime = SHA512("Derive temporary signing key hash input" || <empty>)
стал публичной константой. Следствие: любой, кто может прочитать ОДИН опубликованный
дескриптор, может пересчитать nonce r, вычислить blinded-скаляр
s' = (S − r) · H(R‖PK‖M)⁻¹ mod L и снять ослепление публичным множителем —
восстановив мастер-ключ идентичности onion-сервиса. Без доступа к серверу, без MitM, без брутфорса. Это примитив тихого захвата домена, и он
согласуется с механизмом, наблюдавшимся в недавних взломах форумов.
Исправление: диспетчер теперь передаёт полный расширенный ключ
(gobpk.PrivateKey.PrivKey()); BlindedSignWithTorKey паникует при любом ключе, который
не равен ровно 64 байтам; blindedSignP2 независимо проверяет длину ESK
как защиту в глубину; gobpk.New отклоняет усечённые Tor-ключи во время загрузки.
NewReceivedDescriptor() разбирал и доверял всему, что передавала сеть.
Поскольку субкреденшелы выводятся из blinded-ключа, содержащегося внутри
самого дескриптора, атакующий мог создать криптографически самосогласованные
дескрипторы для чужого onion-адреса, используя свои собственные ключи. Затем
фронтенд переопубликовал бы introduction points атакующего под идентичностью
жертвы — полный перехват трафика, не требующий вообще никакого восстановления ключа.
Исправление: трёхуровневая проверка в новом
VerifyHiddenServiceDescriptorV3(): (1) подпись сертификата под blinded-ключом, (2) подпись дескриптора под сертифицированным ключом подписи и
(3) привязка — blinded-ключ должен совпадать со значением, которое фронтенд вычисляет
независимо из консенсуса (GetBlindingParam + временной период) и
адреса экземпляра. RegisterDescriptor работает по принципу fail-closed: без живого
консенсуса он отказывается регистрировать, а не доверяет слепо.
gobalance-patch/
├── README.md ← этот файл (английский)
├── USAGE.md ← пошаговое руководство по сборке и тестированию (английский)
├── README_ID.md ← ringkasan patch (Bahasa Indonesia)
├── gobalance-security.patch ← unified diff против master@bb1b0f3 (7 файлов, +360/−94)
├── gobalance-patched/ ← полное предварительно пропатченное дерево исходников (drop-in)
│ ├── go.mod / go.sum / main.go
│ ├── pkg/… ← пропатченные библиотеки, включая регрессионные тесты
│ ├── poc/ ← сквозная демонстрация атаки + снимок уязвимого кода
│ ├── cmd/gbdemo/ ← автономный CLI-демо восстановления (+ E2E-тесты) - USAGE #13
│ └── tools/ ← pem2tor.py (конвертер ключей PEM→Tor), get_desc.py (получение дескриптора)
└── gobalance-v1/ ← форк сообщества "GoBalance Enhanced v1.0" (Dread), включён
в том виде, в котором распространяется для тестирования - всё ещё УЯЗВИМ - USAGE #14
# Вариант A - пропатчить свежую копию upstream
git clone https://gitlab.com/n0tr1v/gobalance && cd gobalance
git apply /path/to/gobalance-security.patch
go build ./... && go test ./...
# Вариант B - использовать включённое предварительно пропатченное дерево (самый быстрый)
cd gobalance-patched
go build ./...
go test ./poc/ -v # демонстрация атаки: успешна против уязвимого снимка, неудачна против патча
go test ./... # полный набор: 8 пакетов ok
Полное пошаговое руководство с ожидаемым выводом см. в USAGE.md.
Test01_Vulnerable_MasterKeyRecoveredFromSingleDescriptor.Test02_Patched_TruncatedTorKeyRejected.Test03_Patched_TorPathSignaturesVerifyAsStdEd25519.Test04_Patched_AttackMathYieldsGarbage.TestForgedSelfConsistentDescriptorIsDetected.TestNewReceivedDescriptor_AcceptsHonestDescriptor,
_RejectsTamperedSignature, _RejectsWrongIdentityBinding.Все ключи в PoC генерируются локально во время тестирования. Никакие реальные сервисы не были атакованы.