
Analyse de CVE-2016-3959 et une attaque de preuve de concept contre un serveur SSH Go.
Alex Mullins
9 avril 2016
Récemment, un bug a été découvert dans la bibliothèque crypto du Digital Signature Algorithm (DSA) du langage de programmation Go. Dans cet article, nous allons détailler les spécificités du bug et comment un attaquant pourrait l'exploiter pour lancer une attaque par déni de service contre un serveur SSH Go standard qui utilise la bibliothèque DSA sous-jacente pour authentifier les clients.
La première mention de cette vulnérabilité est apparue dans un message sur la liste de diffusion Open Source Security (oss-sec) à l'adresse http://seclists.org/oss-sec/2016/q2/11.
Go a une boucle infinie dans plusieurs routines de grands entiers qui rend les programmes Go vulnérables à des attaques par déni de service à distance. Les programmes utilisant l'authentification client HTTPS ou les bibliothèques serveur ssh de Go sont exposés à cette vulnérabilité. Ceci est traité dans le CL suivant : https://golang.org/cl/21533
-- Jason Buberel
En résumé, si cette vulnérabilité était exploitée, elle pourrait conduire à une boucle infinie dans le code sous-jacent de la bibliothèque BigNum. Cela consommerait des ressources système en termes de CPU et de mémoire et pourrait éventuellement rendre le programme ou le système lui-même injoignable.
La déclaration ci-dessus indique que SSH ainsi que l'authentification client HTTPS sont affectés, mais après avoir examiné les paquets crypto/tls et net/http de Go, cela semble incorrect. L'authentification client HTTPS peut utiliser les schémas de signature RSA ou ECDSA, mais pas DSA. Voir ci-dessous. Si je me trompe à ce sujet, veuillez me le faire savoir.
https://golang.org/pkg/crypto/tls/#Certificate
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
}
Peu de temps après la publication de ce message sur la liste de diffusion oss-sec, un numéro CVE a été attribué : CVE-2016-3959. Les mainteneurs de Go ont préparé un correctif qui apparaîtra dans les versions 1.5.4 et 1.6.1 qui doivent être publiées le mercredi 13 avril 2016 ; https://groups.google.com/forum/#!topic/golang-nuts/MmSbFHLPo8g.
Pour suivre les exemples de code de cet article, vous aurez besoin de la version 1.6 de Go installée. Suivez les instructions sur https://golang.org/doc/install. Si vous souhaitez télécharger ce document et les exemples de code, vous aurez également besoin de Git. Suivez les instructions sur https://git-scm.com/book/en/v2/Getting-Started-Installing-Git. Pour cloner le dépôt, exécutez la commande suivante dans un terminal :
$ go get github.com/alexmullins/dsa
Cela clonera le dépôt dans votre espace de travail Go.
La section suivante détaillera les spécificités de la vulnérabilité.
Alors, quel est exactement le problème ? Pour répondre à cela, il faut revenir à l'annonce originale sur la liste de diffusion oss-sec. Il n'y a pas beaucoup d'informations à part une explication générale du problème et un lien vers le correctif à https://golang.org/cl/21533. Le message de commit pour ce changement contient ce qui suit :
crypto/dsa : éliminer les PublicKey invalides rapidement
Pour PublicKey.P == 0, Verify échouera. Ne même pas essayer.
--- Robert Griesemer
et le code corrigé :
https://github.com/golang/go/blob/master/src/crypto/dsa/dsa.go#L247
// 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
}
Pour résumer le message de commit et le correctif ci-dessus : dans Go 1.6 et les versions précédentes, il y a un bug dans la fonction Verify du paquet crypto/dsa. Si quelqu'un appelle Verify avec le paramètre de clé publique P défini à 0, cela provoquera une boucle infinie dans l'une des instructions plus bas dans la fonction Verify.
Un petit détour pour expliquer DSA. DSA est un algorithme de signature numérique qui utilise la cryptographie asymétrique pour signer un message, qui peut ensuite être utilisé pour garantir que le message a bien été envoyé par l'expéditeur/détenteur de la clé privée. Un exemple simple : Alice envoie un message à Bob lui indiquant où et quand ils doivent se retrouver pour déjeuner. Bob, cependant, veut être sûr que c'est bien Alice qui lui a envoyé le message et non quelqu'un d'autre. Pour que cela fonctionne, Alice signera le message avec sa clé privée et Bob pourra vérifier la signature d'Alice avec sa clé publique que Bob connaît. Personne d'autre que le détenteur de la clé privée ne peut signer un message qui peut ensuite être vérifié par la clé publique correspondante (c'est du moins l'idée).
Pour fonctionner, DSA a besoin de 5 grands nombres. Les 3 premiers nombres sont connus sous le nom de paramètres DSA P, Q et G. Ceux-ci définissent le groupe sous-jacent et le générateur de groupe. Ces nombres peuvent être correctement créés en appelant dsa.GenerateParameters().
type Parameters struct {
P, Q, G *big.Int
}
Les deux derniers nombres nécessaires pour DSA sont la clé privée X et la clé publique correspondante Y. Ces nombres peuvent également être créés en appelant dsa.GenerateKey().
type PrivateKey struct {
PublicKey
X *big.Int
}
type PublicKey struct {
Parameters
Y *big.Int
}
C'est tout ce que vous devez savoir sur DSA pour suivre. Pour plus d'informations, voir la norme NIST : http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf ou la page wikipedia : https://en.wikipedia.org/wiki/Digital_Signature_Algorithm.
Revenons au sujet ; où se trouve la boucle infinie dans la fonction Verify ? Avec un peu plus de recherche, vous constaterez que le code bloque sur :
v := u1.Exp(pub.G, u1, pub.P)
Voici le commentaire de la méthode Exp() :
// 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 {