
Correctif de sécurité et preuve de concept pour l'équilibreur de charge oignon GoBalance, couvrant la récupération de la clé maître via blindedSign et l'acceptation de descripteurs falsifiés, avec des tests de régression.
Paquet d'avis pour gitlab.com/n0tr1v/gobalance - branche master, commit bb1b0f3 ("fix crash").
Statut : CRITIQUE - deux chemins indépendants de prise de contrôle totale. La branche amont patch1 ne corrige aucun des deux.
Ce paquet contient un correctif de sécurité complet, une preuve de concept de bout en bout pour les deux chemins d'attaque, et des tests de régression prouvant que les correctifs tiennent. Il accompagne le rapport complet d'analyse de vulnérabilité ("Laporan Analisis Keamanan GoBalance") préparé en lien avec les récents incidents de prise de contrôle de domaines onion affectant deux forums. Un guide de compilation et de test étape par étape se trouve dans USAGE.md.
| # | Vulnérabilité | Sévérité | Impact | Statut |
|---|---|---|---|---|
| 1 | La clé d'identité maître fuit via blindedSign (préfixe de nonce constant) | CRITIQUE | Récupération complète de l'identité onion à partir d'un seul descripteur public | Corrigé |
| 2 | RegisterDescriptor accepte des descripteurs d'instance falsifiés (aucune vérification de signature / liaison) | CRITIQUE | Détournement du trafic de n'importe quel frontend GoBalance | Corrigé |
| 3 | Chemin RNG déterministe, initialisé par le temps, dans pkg/brand | ÉLEVÉE (piège) | Matériel de clé prévisible pour tout ce qui l'utilise | Supprimé |
| 4 | Le mélange des points d'introduction utilise math/rand | FAIBLE | Aléa faible dans du code adjacent au protocole | Remplacé par crypto/rand |
Le dispatcher blindedSign() dans pkg/stem/descriptor/hidden_service.go passait
identityKey.Seed() - le scalaire brut de 32 octets a - à
BlindedSignWithTorKey(). Les clés au format Tor sont des clés étendues : 64 octets
(a || h), où h est la clé PRF qui dérive le préfixe de nonce
par signature. En l'absence de h, l'entrée de dérivation du nonce était vide et
kPrime = SHA512("Derive temporary signing key hash input" || <empty>)
devenait une constante publique. Conséquence : quiconque peut lire UN seul
descripteur publié peut recalculer le nonce r, résoudre pour le scalaire aveuglé
s' = (S − r) · H(R‖PK‖M)⁻¹ mod L, et le désaveugler avec un multiplicateur public -
récupérant ainsi la clé d'identité maître du service onion. Aucun accès serveur, aucun
MitM, aucune force brute. Il s'agit d'une primitive silencieuse de prise de contrôle de domaine,
cohérente avec le mécanisme observé dans les récents détournements de forums.
Correctif : le dispatcher transmet désormais la clé étendue complète
(gobpk.PrivateKey.PrivKey()) ; BlindedSignWithTorKey panique sur toute clé qui
n'a pas exactement 64 octets ; blindedSignP2 impose indépendamment la longueur ESK
en défense en profondeur ; gobpk.New rejette les clés Tor tronquées au chargement.
NewReceivedDescriptor() analysait et faisait confiance à tout ce que le réseau lui fournissait.
Parce que les sous-identifiants sont dérivés de la clé aveuglée portée à l'intérieur
du descripteur lui-même, un attaquant pouvait forger des descripteurs cryptographiquement
auto-cohérents pour l'adresse onion de quelqu'un d'autre en utilisant ses propres clés. Le
frontend republiait alors les points d'introduction de l'attaquant sous l'identité de la
victime - un détournement complet du trafic qui ne nécessite aucune récupération de clé.
Correctif : vérification à trois couches dans le nouveau
VerifyHiddenServiceDescriptorV3() : (1) signature du certificat sous la
clé aveuglée, (2) signature du descripteur sous la clé de signature certifiée, et
(3) liaison - la clé aveuglée doit être égale à la valeur que le frontend calcule
indépendamment à partir du consensus (GetBlindingParam + période temporelle) et de
l'adresse de l'instance. RegisterDescriptor est en échec fermé : sans consensus
actif, il refuse d'enregistrer plutôt que de faire aveuglément confiance.
gobalance-patch/
├── README.md ← ce fichier (anglais)
├── USAGE.md ← guide de compilation et de test étape par étape (anglais)
├── README_ID.md ← ringkasan patch (Bahasa Indonesia)
├── gobalance-security.patch ← diff unifié contre master@bb1b0f3 (7 fichiers, +360/−94)
├── gobalance-patched/ ← arborescence source complète pré-patchée (prête à l'emploi)
│ ├── go.mod / go.sum / main.go
│ ├── pkg/… ← bibliothèques patchées, incl. tests de régression
│ ├── poc/ ← démo d'attaque de bout en bout + instantané du code vulnérable
│ ├── cmd/gbdemo/ ← CLI de démo de récupération autonome (+ tests E2E) - USAGE #13
│ └── tools/ ← pem2tor.py (convertisseur de clé PEM→Tor), get_desc.py (récupération de descripteur)
└── gobalance-v1/ ← fork communautaire "GoBalance Enhanced v1.0" (Dread), fourni
tel que distribué pour les tests - toujours VULNÉRABLE - USAGE #14
# Option A - patcher une copie amont fraîche
git clone https://gitlab.com/n0tr1v/gobalance && cd gobalance
git apply /path/to/gobalance-security.patch
go build ./... && go test ./...
# Option B - utiliser l'arborescence pré-patchée fournie (la plus rapide)
cd gobalance-patched
go build ./...
go test ./poc/ -v # démo d'attaque : réussit contre l'instantané vulnérable, échoue contre le patch
go test ./... # suite complète : 8 paquets ok
Voir USAGE.md pour la procédure complète avec la sortie attendue.
Test01_Vulnerable_MasterKeyRecoveredFromSingleDescriptor.Test02_Patched_TruncatedTorKeyRejected.Test03_Patched_TorPathSignaturesVerifyAsStdEd25519.Test04_Patched_AttackMathYieldsGarbage.TestForgedSelfConsistentDescriptorIsDetected.TestNewReceivedDescriptor_AcceptsHonestDescriptor,
_RejectsTamperedSignature, _RejectsWrongIdentityBinding.Toutes les clés de la PoC sont générées localement au moment des tests. Aucun service réel n'a été ciblé.