
Parche de seguridad y prueba de concepto para el balanceador de carga onion GoBalance, que abarca la recuperación de la clave maestra mediante blindedSign y la aceptación de descriptores falsificados, con pruebas de regresión.
Paquete de aviso para gitlab.com/n0tr1v/gobalance - rama master, commit bb1b0f3 ("fix crash").
Estado: CRÍTICO - dos rutas independientes de toma de control total. La rama upstream patch1 no corrige ninguna de ellas.
Este paquete contiene un parche de seguridad completo, una prueba de concepto de extremo a extremo para ambas rutas de ataque, y pruebas de regresión que demuestran que las correcciones se mantienen. Acompaña al informe completo de análisis de vulnerabilidades ("Laporan Analisis Keamanan GoBalance") preparado en relación con los recientes incidentes de toma de control de dominios onion que afectaron a dos foros. Una guía paso a paso de compilación y pruebas está en USAGE.md.
| # | Vulnerabilidad | Severidad | Impacto | Estado |
|---|---|---|---|---|
| 1 | La clave de identidad maestra se filtra a través de blindedSign (prefijo de nonce constante) | CRÍTICA | Recuperación completa de la identidad onion a partir de un único descriptor público | Corregida |
| 2 | RegisterDescriptor acepta descriptores de instancia falsificados (sin verificación de firma / vinculación) | CRÍTICA | Secuestro de tráfico de cualquier frontend de GoBalance | Corregida |
| 3 | Ruta de RNG determinista con semilla temporal en pkg/brand | ALTA (trampa) | Material de clave predecible para cualquier cosa que lo use | Eliminada |
| 4 | La mezcla de puntos de introducción usa math/rand | BAJA | Aleatoriedad débil en código adyacente al protocolo | Reemplazada por crypto/rand |
El despachador blindedSign() en pkg/stem/descriptor/hidden_service.go pasaba
identityKey.Seed() - el escalar crudo de 32 bytes a - a
BlindedSignWithTorKey(). Las claves en formato Tor son claves extendidas: 64 bytes
(a || h), donde h es la clave PRF que deriva el prefijo de nonce
por firma. Al faltar h, la entrada de derivación del nonce quedaba vacía y
kPrime = SHA512("Derive temporary signing key hash input" || <empty>)
se convertía en una constante pública. Consecuencia: cualquiera que pueda leer UN descriptor
publicado puede recalcular el nonce r, resolver el escalar cegado
s' = (S − r) · H(R‖PK‖M)⁻¹ mod L, y descegarlo con un multiplicador público -
recuperando la clave de identidad maestra del servicio onion. Sin acceso al servidor, sin
MitM, sin fuerza bruta. Esto es una primitiva silenciosa de toma de control de dominio y es
consistente con el mecanismo observado en los recientes secuestros de foros.
Corrección: el despachador ahora reenvía la clave extendida completa
(gobpk.PrivateKey.PrivKey()); BlindedSignWithTorKey entra en pánico con cualquier clave que
no tenga exactamente 64 bytes; blindedSignP2 aplica de forma independiente la longitud de ESK
como defensa en profundidad; gobpk.New rechaza claves Tor truncadas en el momento de carga.
NewReceivedDescriptor() analizaba y confiaba en lo que la red le entregara.
Debido a que las subcredenciales se derivan de la clave cegada contenida dentro del
propio descriptor, un atacante podía acuñar descriptores criptográficamente autoconsistentes
para la dirección onion de otra persona usando sus propias claves. El
frontend entonces republicaría los puntos de introducción del atacante bajo la identidad de la
víctima - un secuestro de tráfico completo que no requiere recuperación de claves
en absoluto.
Corrección: verificación en tres capas en el nuevo
VerifyHiddenServiceDescriptorV3(): (1) firma del certificado bajo la
clave cegada, (2) firma del descriptor bajo la clave de firma certificada, y
(3) vinculación - la clave cegada debe ser igual al valor que el frontend calcula
de forma independiente a partir del consenso (GetBlindingParam + período de tiempo) y la
dirección de la instancia. RegisterDescriptor falla de forma cerrada: sin un
consenso activo se niega a registrar en lugar de confiar ciegamente.
gobalance-patch/
├── README.md ← este archivo (inglés)
├── USAGE.md ← guía paso a paso de compilación y pruebas (inglés)
├── README_ID.md ← ringkasan patch (Bahasa Indonesia)
├── gobalance-security.patch ← diff unificado contra master@bb1b0f3 (7 archivos, +360/−94)
├── gobalance-patched/ ← árbol de código fuente completo pre-parcheado (drop-in)
│ ├── go.mod / go.sum / main.go
│ ├── pkg/… ← bibliotecas parcheadas, incl. pruebas de regresión
│ ├── poc/ ← demo de ataque de extremo a extremo + snapshot del código vulnerable
│ ├── cmd/gbdemo/ ← CLI de demostración de recuperación independiente (+ pruebas E2E) - USAGE #13
│ └── tools/ ← pem2tor.py (conversor de clave PEM→Tor), get_desc.py (obtención de descriptor)
└── gobalance-v1/ ← fork comunitario "GoBalance Enhanced v1.0" (Dread), incluido
tal como se distribuye para pruebas - aún VULNERABLE - USAGE #14
# Opción A - parchear una copia limpia del upstream
git clone https://gitlab.com/n0tr1v/gobalance && cd gobalance
git apply /path/to/gobalance-security.patch
go build ./... && go test ./...
# Opción B - usar el árbol pre-parcheado incluido (la más rápida)
cd gobalance-patched
go build ./...
go test ./poc/ -v # demo de ataque: tiene éxito contra el snapshot vulnerable, falla contra el parche
go test ./... # suite completa: 8 paquetes ok
Consulta USAGE.md para el recorrido completo con la salida esperada.
Test01_Vulnerable_MasterKeyRecoveredFromSingleDescriptor.Test02_Patched_TruncatedTorKeyRejected.Test03_Patched_TorPathSignaturesVerifyAsStdEd25519.Test04_Patched_AttackMathYieldsGarbage.TestForgedSelfConsistentDescriptorIsDetected.TestNewReceivedDescriptor_AcceptsHonestDescriptor,
_RejectsTamperedSignature, _RejectsWrongIdentityBinding.Todas las claves en la PoC se generan localmente en el momento de la prueba. No se atacó ningún servicio real.