
Patch di sicurezza e proof-of-concept per il load balancer onion GoBalance, che copre il recupero della master key tramite blindedSign e l'accettazione di descriptor contraffatti, con test di regressione.
Pacchetto di advisory per gitlab.com/n0tr1v/gobalance - branch master, commit bb1b0f3 ("fix crash").
Stato: CRITICO - due percorsi indipendenti di takeover completo. Il branch upstream patch1 non corregge nessuno dei due.
Questo pacchetto contiene una patch di sicurezza completa, un proof-of-concept end-to-end per entrambi i percorsi di attacco e test di regressione che dimostrano che le correzioni tengono. Accompagna il report completo di analisi delle vulnerabilità ("Laporan Analisis Keamanan GoBalance") preparato in relazione ai recenti incidenti di takeover di domini onion che hanno colpito due forum. Una guida passo-passo per build e test è in USAGE.md.
| # | Vulnerabilità | Gravità | Impatto | Stato |
|---|---|---|---|---|
| 1 | La chiave identità master trapela tramite blindedSign (prefisso nonce costante) | CRITICA | Recupero completo dell'identità onion da un singolo descriptor pubblico | Corretta |
| 2 | RegisterDescriptor accetta descriptor di istanza contraffatti (nessuna verifica di firma / binding) | CRITICA | Hijack del traffico di qualsiasi frontend GoBalance | Corretta |
| 3 | Percorso RNG deterministico, seedato temporalmente, in pkg/brand | ALTA (footgun) | Materiale di chiave prevedibile per tutto ciò che lo utilizza | Rimosso |
| 4 | Lo shuffle degli introduction-point usa math/rand | BASSA | Randomicità debole in codice adiacente al protocollo | Sostituito con crypto/rand |
Il dispatcher blindedSign() in pkg/stem/descriptor/hidden_service.go passava
identityKey.Seed() - lo scalare grezzo di 32 byte a - a
BlindedSignWithTorKey(). Le chiavi in formato Tor sono chiavi estese: 64 byte
(a || h), dove h è la chiave PRF che deriva il prefisso nonce per-firma. Con h mancante, l'input di derivazione del nonce era vuoto e
kPrime = SHA512("Derive temporary signing key hash input" || <empty>)
è diventato una costante pubblica. Conseguenza: chiunque possa leggere UN
descriptor pubblicato può ricalcolare il nonce r, risolvere per lo scalare blinded
s' = (S − r) · H(R‖PK‖M)⁻¹ mod L, e unblindarlo con un moltiplicatore pubblico -
recuperando la chiave identità master del servizio onion. Nessun accesso al server, nessun
MitM, nessuna brute force. Si tratta di una primitiva silenziosa di domain-takeover ed è
coerente con il meccanismo osservato nei recenti hijack dei forum.
Fix: il dispatcher ora inoltra la chiave estesa completa
(gobpk.PrivateKey.PrivKey()); BlindedSignWithTorKey va in panic su qualsiasi chiave che
non sia esattamente di 64 byte; blindedSignP2 impone indipendentemente la lunghezza ESK
come difesa in profondità; gobpk.New rifiuta le chiavi Tor troncate al momento del load.
NewReceivedDescriptor() analizzava e considerava attendibile qualunque cosa la rete gli passasse.
Poiché le subcredenziali sono derivate dalla chiave blinded trasportata all'interno del
descriptor stesso, un attaccante poteva coniare descriptor crittograficamente auto-consistenti
per l'indirizzo onion di qualcun altro usando le proprie chiavi. Il
frontend avrebbe poi ripubblicato gli introduction point dell'attaccante sotto l'identità
della vittima - un hijack completo del traffico che non richiede alcun recupero di chiave.
Fix: verifica a tre livelli nel nuovo
VerifyHiddenServiceDescriptorV3(): (1) firma del certificato sotto la
chiave blinded, (2) firma del descriptor sotto la chiave di firma certificata, e
(3) binding - la chiave blinded deve essere uguale al valore che il frontend calcola
indipendentemente dal consensus (GetBlindingParam + periodo temporale) e
dall'indirizzo dell'istanza. RegisterDescriptor è fail-closed: senza un
consensus attivo rifiuta di registrare invece di fidarsi ciecamente.
gobalance-patch/
├── README.md ← questo file (inglese)
├── USAGE.md ← guida passo-passo per build e test (inglese)
├── README_ID.md ← ringkasan patch (Bahasa Indonesia)
├── gobalance-security.patch ← diff unificato rispetto a master@bb1b0f3 (7 file, +360/−94)
├── gobalance-patched/ ← albero sorgente completo pre-patchato (drop-in)
│ ├── go.mod / go.sum / main.go
│ ├── pkg/… ← librerie patchate, incl. test di regressione
│ ├── poc/ ← demo end-to-end dell'attacco + snapshot del codice vulnerabile
│ ├── cmd/gbdemo/ ← CLI standalone per la demo di recupero (+ test E2E) - USAGE #13
│ └── tools/ ← pem2tor.py (convertitore chiavi PEM→Tor), get_desc.py (fetch descriptor)
└── gobalance-v1/ ← fork della community "GoBalance Enhanced v1.0" (Dread), incluso
come distribuito per i test - ancora VULNERABILE - USAGE #14
# Option A - patch a fresh upstream checkout
git clone https://gitlab.com/n0tr1v/gobalance && cd gobalance
git apply /path/to/gobalance-security.patch
go build ./... && go test ./...
# Option B - use the bundled pre-patched tree (fastest)
cd gobalance-patched
go build ./...
go test ./poc/ -v # attack demo: succeeds vs vulnerable snapshot, fails vs patch
go test ./... # full suite: 8 packages ok
Vedi USAGE.md per la procedura completa con l'output atteso.
Test01_Vulnerable_MasterKeyRecoveredFromSingleDescriptor.Test02_Patched_TruncatedTorKeyRejected.Test03_Patched_TorPathSignaturesVerifyAsStdEd25519.Test04_Patched_AttackMathYieldsGarbage.TestForgedSelfConsistentDescriptorIsDetected.TestNewReceivedDescriptor_AcceptsHonestDescriptor,
_RejectsTamperedSignature, _RejectsWrongIdentityBinding.Tutte le chiavi nel PoC sono generate localmente al momento del test. Nessun servizio reale è stato preso di mira.