
Patch de segurança e prova de conceito para o balanceador de carga onion GoBalance, cobrindo a recuperação da chave mestra via blindedSign e a aceitação de descritores forjados, com testes de regressão.
Pacote de aviso para gitlab.com/n0tr1v/gobalance - branch master, commit bb1b0f3 ("fix crash").
Status: CRÍTICO - dois caminhos independentes de tomada de controle total. O branch patch1 upstream não corrige nenhum deles.
Este pacote contém um patch de segurança completo, uma prova de conceito ponta a ponta para ambos os caminhos de ataque e testes de regressão que comprovam que as correções se mantêm. Ele acompanha o relatório completo de análise de vulnerabilidade ("Laporan Analisis Keamanan GoBalance") preparado em conexão com os recentes incidentes de tomada de controle de domínios onion que afetaram dois fóruns. Um guia passo a passo de compilação e teste está em USAGE.md.
| # | Vulnerabilidade | Severidade | Impacto | Status |
|---|---|---|---|---|
| 1 | Chave de identidade master vaza através de blindedSign (prefixo de nonce constante) | CRÍTICO | Recuperação completa da identidade onion a partir de um único descritor público | Corrigido |
| 2 | RegisterDescriptor aceita descritores de instância forjados (sem verificação de assinatura / vinculação) | CRÍTICO | Sequestro de tráfego de qualquer frontend GoBalance | Corrigido |
| 3 | Caminho de RNG determinístico, semeado por tempo, em pkg/brand | ALTO (armadilha) | Material de chave previsível para qualquer coisa que o utilize | Removido |
| 4 | Embaralhamento de pontos de introdução usa math/rand | BAIXO | Aleatoriedade fraca em código adjacente ao protocolo | Substituído por crypto/rand |
O dispatcher blindedSign() em pkg/stem/descriptor/hidden_service.go passava
identityKey.Seed() - o escalar bruto de 32 bytes a - para
BlindedSignWithTorKey(). Chaves em formato Tor são chaves estendidas: 64 bytes
(a || h), onde h é a chave PRF que deriva o prefixo de nonce por assinatura.
Com h ausente, a entrada de derivação do nonce ficou vazia e
kPrime = SHA512("Derive temporary signing key hash input" || <empty>)
tornou-se uma constante pública. Consequência: qualquer um que consiga ler UM
descritor publicado pode recalcular o nonce r, resolver para o escalar cego
s' = (S − r) · H(R‖PK‖M)⁻¹ mod L, e desvendá-lo com um multiplicador público -
recuperando a chave de identidade master do serviço onion. Sem acesso ao servidor, sem
MitM, sem força bruta. Esta é uma primitiva silenciosa de tomada de controle de domínio e é
consistente com o mecanismo observado nos recentes sequestros de fóruns.
Correção: o dispatcher agora encaminha a chave estendida completa
(gobpk.PrivateKey.PrivKey()); BlindedSignWithTorKey entra em pânico com qualquer chave que
não tenha exatamente 64 bytes; blindedSignP2 impõe independentemente o comprimento da ESK
como defesa em profundidade; gobpk.New rejeita chaves Tor truncadas no momento do carregamento.
NewReceivedDescriptor() analisava e confiava em qualquer coisa que a rede lhe entregasse.
Como as subcredenciais são derivadas da chave cega carregada dentro do
próprio descritor, um atacante poderia cunhar descritores criptograficamente autoconsistentes
para o endereço onion de outra pessoa usando suas próprias chaves. O
frontend então republicaria os pontos de introdução do atacante sob a identidade da
vítima - um sequestro de tráfego completo que não requer nenhuma recuperação de chave.
Correção: verificação em três camadas no novo
VerifyHiddenServiceDescriptorV3(): (1) assinatura do certificado sob a
chave cega, (2) assinatura do descritor sob a chave de assinatura certificada, e
(3) vinculação - a chave cega deve ser igual ao valor que o frontend calcula
independentemente a partir do consenso (GetBlindingParam + período de tempo) e do
endereço da instância. RegisterDescriptor é fail-closed: sem um
consenso ativo, ele se recusa a registrar em vez de confiar cegamente.
gobalance-patch/
├── README.md ← este arquivo (Inglês)
├── USAGE.md ← guia passo a passo de compilação e teste (Inglês)
├── README_ID.md ← ringkasan patch (Bahasa Indonesia)
├── gobalance-security.patch ← diff unificado contra master@bb1b0f3 (7 arquivos, +360/−94)
├── gobalance-patched/ ← árvore de código-fonte completa pré-patchada (drop-in)
│ ├── go.mod / go.sum / main.go
│ ├── pkg/… ← bibliotecas corrigidas, incl. testes de regressão
│ ├── poc/ ← demonstração de ataque ponta a ponta + snapshot do código vulnerável
│ ├── cmd/gbdemo/ ← CLI de demonstração de recuperação autônoma (+ testes E2E) - USAGE #13
│ └── tools/ ← pem2tor.py (conversor de chave PEM→Tor), get_desc.py (busca de descritor)
└── gobalance-v1/ ← fork da comunidade "GoBalance Enhanced v1.0" (Dread), incluído
conforme distribuído para testes - ainda VULNERÁVEL - USAGE #14
# Opção A - aplicar patch em um checkout upstream limpo
git clone https://gitlab.com/n0tr1v/gobalance && cd gobalance
git apply /path/to/gobalance-security.patch
go build ./... && go test ./...
# Opção B - usar a árvore pré-patchada incluída (mais rápido)
cd gobalance-patched
go build ./...
go test ./poc/ -v # demonstração de ataque: sucesso contra snapshot vulnerável, falha contra patch
go test ./... # suíte completa: 8 pacotes ok
Consulte USAGE.md para o passo a passo completo com a saída esperada.
Test01_Vulnerable_MasterKeyRecoveredFromSingleDescriptor.Test02_Patched_TruncatedTorKeyRejected.Test03_Patched_TorPathSignaturesVerifyAsStdEd25519.Test04_Patched_AttackMathYieldsGarbage.TestForgedSelfConsistentDescriptorIsDetected.TestNewReceivedDescriptor_AcceptsHonestDescriptor,
_RejectsTamperedSignature, _RejectsWrongIdentityBinding.Todas as chaves na PoC são geradas localmente no momento do teste. Nenhum serviço real foi alvo.