La faille

En effet, la faille est due à la fonction _gnutls_x509_verify_certificate qui, comme son nom l'indique, vérifie une chaîne de certificats par rapport à une liste d'autorités de certification (AC) de confiance (trusted CA). En théorie, cette chaîne doit se terminer par un certificat signé par une autorité de confiance, ceci permettant d'authentifier l'autre partie d'une communication. Par la suite nous considérerons le cas d'un client se connectant à un serveur Web et voulant vérifier l'identité de ce dernier.

Dans le cas usuel, le serveur ne fournit qu'un seul certificat, signé par une AC reconnue par le navigateur du client, mais il est également possible de fournir une chaîne de certificats.

Voyons donc le corps de la fonction vulnérable :

static unsigned int
_gnutls_x509_verify_certificate (const gnutls_x509_crt_t * certificate_list,
				 int clist_size,
				 const gnutls_x509_crt_t * trusted_cas,
				 int tcas_size,
				 const gnutls_x509_crl_t * CRLs,
				 int crls_size, unsigned int flags)
{
  int i = 0, ret;
  unsigned int status = 0, output;
  /* Verify the last certificate in the certificate path
   * against the trusted CA certificate list.
   *
   * If no CAs are present returns CERT_INVALID. Thus works
   * in self signed etc certificates.
   */
  ret =
    _gnutls_verify_certificate2 (certificate_list[clist_size - 1],
				 trusted_cas, tcas_size, flags, &output);
  if (ret == 0)
    {
    [...] (return)
    }
  [...]
  /* Check if the last certificate in the path is self signed.
   * In that case ignore it (a certificate is trusted only if it
   * leads to a trusted party by us, not the server's).
   */
  if (gnutls_x509_crt_check_issuer (certificate_list[clist_size - 1],
				    certificate_list[clist_size - 1]) > 0
      && clist_size > 0)
    {
      clist_size--;
    }
  /* Verify the certificate path (chain) 
   */
  for (i = clist_size - 1; i > 0; i--)
    {
      if (i - 1 < 0)
	break;
	[...]
      if ((ret =
	   _gnutls_verify_certificate2 (certificate_list[i - 1],
					&certificate_list[i], 1, flags,
					NULL)) == 0)
	{
	  status |= GNUTLS_CERT_INVALID;
	  return status;
	}
    }
  return 0;
}

En clair, les étapes sont les suivantes:

  • Vérification du dernier certificat de la chaîne vis à vis des AC de confiance
  • Si le certificat n'est pas signé par une AC reconnue, la fonction retourne une erreur
  • Si le dernier certificat est auto-signé, alors on le supprime de la liste
  • Vérification de la signature de chaque certificat de la chaîne par rapport à celui de niveau supérieur

Si l'on lit cette liste attentivement, on constate que si le dernier certificat de la chaîne est valide et auto-signé alors l'avant dernier sera implicitement considéré comme valide. A priori cela ne semble pas problématique ... sauf que tous (?) les certificats des AC standards sont auto-signés.

L'exploitation de la faille est donc extrêmement aisée, comme on peut le constater ici ou dans le programme de test rajouté dans GnuTLS.

Il suffit en effet pour usurper l'identité d'un serveur de présenter au client une chaîne de certificats ainsi constituée :

  • Un certificat auto-signé ayant le même Common Name que le serveur légitime
  • Un certificat de confiance auto-signé : par exemple celui de Thawte.

GnuTLS va vérifier si le certificat de Thawte est de confiance, puis le supprimer. Ensuite, la boucle de vérification ne sera même pas utilisée, clist_size vallant maintenant 0, la fonction va retourner 0 à l'appelant, la chaîne est donc considérée comme valide.

Il est également intéressant de rechercher l'origine de la faille. L'historique du dépôt GIT du projet nous permet de retrouver le commit responsable de la faille. Le message de commit dit "Corrected a bug in certificate verification that could lead to a trusted certificate path to be marked as non-trusted, if it included the last self-signed certificate in the chain". Le problème a donc été ajouté en tentant de renforcer la sécurité ! De plus, si on étudie attentivement le premier patch publié pour corriger la vulnérabilité (GnuTLS v2.6.1), celui-ci déplace la suppression du dernier certificat de la chaîne (si celui-ci est auto-signé) au début de la fonction, ce qui est complètement idiot : une chaîne terminée par un certificat auto-signé sera amputée et donc incomplète, y compris lorsque la chaîne n'a qu'un élément, cas qui provoque des segfaults. Au final, la correction, correspondant à la version 2.6.2, consiste à retirer complètement la suppression des certificats auto-signés de la chaîne de vérification, ce qui est logique, un utilisateur peut vouloir truster un certificat auto-signé !

Conséquences

Les problèmes liés à cette faille sont simples :

  • un attaquant pouvant faire un man in the middle peut se faire passer pour le serveur légitime sans alerte
  • un attaquant peut s'authentifier en temps que client légitime sur un serveur utilisant l'authentification par certificats

Le premier cas peut se combiner avec LA faille DNS ou alors n'importe quelle technique sur un réseau local. Le deuxième est plus problématique, vu qu'il ne nécessite pas de détournement de flux, cependant le nombre de serveurs utilisant l'authentification par certificat et GnuTLS doit être relativement faible.

Quels sont les logiciels impactés ? La réponse est relativement difficile à donner mais on peut se faire une idée sur Debian à l'aide de l'outil apt-rdepends : apt-rdepends -r libgnutls 26 retourne 193 packages dépendants directement de GnuTLS, notamment lftp, curl. De nombreux projets sont liés à GnuTLS au lieu de OpenSSL pour des questions de licence : la licence OpenSSL est incompatible avec la GPL. Certains projets choisissent donc GnuTLS, malgré ses performances atroces et son code parfois très moche.

Buzz ?

Plus que la vulnérabilité en elle-même, plusieurs points méritent selon moi l'attention. Premièrement, cette vulnérabilité n'a absolument pas fait parler d'elle ! Certes celle-ci n'est pas aussi critique que celle de OpenSSL dans Debian, mais GnuTLS est quand même très utilisée. Mais bon, il aurait sûrement suffi d'annoncer la plus grosse vulnérabilité du mois, ne pas releaser les détails, etc. pour la transformer en faille médiatisée ;) Je vous renvoie d'ailleurs sur un post plus ancien de Sid qui reprend des failles peu médiatisées et à celui de FX sur la perception des vulnérablités.

Mais le point le plus important reste la façon dont les développeurs de GnuTLS ont géré cette faille : la correction publiée a été complètement catastrophique, comme vu plus haut. Le premier patch a été sorti en vitesse sans même vérifier qu'il fonctionnait correctement ! Compte tenu de l'importance de cette bibliothèque, comme on dit, c'est ballot.

Enfin, je conclurai en vous invitant à ne pas utiliser GnuTLS dans vos logiciels (regardez le code pour vous en convaincre, si vous ne l'êtes pas déjà), utilisez OpenSSL (ou XySSL ;), rajoutez la clause qui va bien dans la GPL s'il le faut. Finalement, tout ça, c'est à cause de la licence d'OpenSSL, non compatible avec la GPL. Si celle-ci l'avait été, il n'y aurait probablement pas eu autant de volonté de créer une alternative sous licence GPL...

P.S. : Merci à Sid pour m'avoir laissé une petite place sur son (très bon) blog :)

Trou