NUL bytes in X.509 CN/SAN fields [GNUTLS-SA-2009-4] [CVE-2009-2730]

Simon Josefsson simon at josefsson.org
Fri Aug 14 12:02:27 CEST 2009


Research presented independently by Dan Kaminsky and Moxie Marlinspike
at BlackHat09 demonstrated a vulnerability related to NUL bytes in X.509
certificate name fields.  Attackers needs to go to a CA and acquire a
certificate for an invalid name.  To attack GnuTLS, the attacker needs
to acquire such an invalid certificate for each TLS server to be
attacked.  The attacker is then able to use the certificate to become a
man-in-the-middle on server authenticated channels.  The client will
receive the attacker's certificate, so the attack leaves traces with the
client.  The attack does not work for client-authenticated TLS channels.

To make client notice the attack and reject it, a few separate aspects
needs to be resolved:

1) When displaying a certificate to the user, GnuTLS needs to be fixed
so that it does not truncate strings in CN/SAN fields to the first NUL.

2) When comparing the CN/SAN fields to a hostname, it has to make sure
it compares the entire string, and not only up until the first NUL.

We recommend all users to upgrade to version 2.8.3 which solves these
concerns.

For people using older releases, or wants to read the minimal patch
required to solve the problem, I have prepared patches that applies to
version 2.8.1 -- the last stable releases that didn't have this
vulnerability.

To solve 1) apply PATCH 1 included below.  The patches in git
corresponding to this patch is (they have to be applied in order):

http://git.savannah.gnu.org/cgit/gnutls.git/commit/?h=gnutls_2_8_x&id=a431be86124f900c4082e82d32917f86fcce461a
http://git.savannah.gnu.org/cgit/gnutls.git/commit/?h=gnutls_2_8_x&id=74b6d92f9675ce4e03642c4d6ced4a3a614b07f6
http://git.savannah.gnu.org/cgit/gnutls.git/commit/?h=gnutls_2_8_x&id=40081594e3de518b998f3e5177ed5a9f7707f2e8

To solve 2) apply PATCH 2 included below.  This patch was supplied by
Tomas Hoger <thoger at redhat.com>.  The patches in git corresponding to
this patch is (they have to be applied in order):

http://git.savannah.gnu.org/cgit/gnutls.git/patch/?id=5a58e9d33448235377afd5fbfcee1683dc70eae3
http://git.savannah.gnu.org/cgit/gnutls.git/patch/?id=1ea190d216767dd4ab93b87361cbcb9d4fb3aafc

I have verified that the patches work for v2.6.6 as well if you manually
resolve one failed hunk.

/Simon

PATCH 1

diff -ur gnutls-2.8.1.orig/lib/x509/common.c gnutls-2.8.1/lib/x509/common.c
--- gnutls-2.8.1.orig/lib/x509/common.c	2009-06-02 20:59:32.000000000 +0200
+++ gnutls-2.8.1/lib/x509/common.c	2009-08-14 11:56:17.000000000 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation
+ * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation
  *
  * Author: Nikos Mavrogiannopoulos
  *
@@ -242,6 +242,10 @@
     {
       str[len] = 0;
 
+      /* Refuse to deal with strings containing NULs. */
+      if (strlen (str) != len)
+	return GNUTLS_E_ASN1_DER_ERROR;
+
       if (res)
 	_gnutls_str_cpy (res, *res_size, str);
       *res_size = len;
@@ -291,25 +295,27 @@
 	    non_printable = 0;
 	}
 
-      if (res)
+      if (non_printable == 0)
 	{
-	  if (non_printable == 0)
-	    {
-	      str[len] = 0;
-	      _gnutls_str_cpy (res, *res_size, str);
-	      *res_size = len;
-	    }
-	  else
+	  str[len] = 0;
+
+	  /* Refuse to deal with strings containing NULs. */
+	  if (strlen (str) != len)
+	    return GNUTLS_E_ASN1_DER_ERROR;
+
+	  if (res)
+	    _gnutls_str_cpy (res, *res_size, str);
+	  *res_size = len;
+	}
+      else
+	{
+	  result = _gnutls_x509_data2hex (str, len, res, res_size);
+	  if (result < 0)
 	    {
-	      result = _gnutls_x509_data2hex (str, len, res, res_size);
-	      if (result < 0)
-		{
-		  gnutls_assert ();
-		  return result;
-		}
+	      gnutls_assert ();
+	      return result;
 	    }
 	}
-
     }
 
   return 0;
diff -ur gnutls-2.8.1.orig/lib/x509/output.c gnutls-2.8.1/lib/x509/output.c
--- gnutls-2.8.1.orig/lib/x509/output.c	2009-06-02 20:59:32.000000000 +0200
+++ gnutls-2.8.1/lib/x509/output.c	2009-08-14 11:56:17.000000000 +0200
@@ -354,6 +354,17 @@
 	  return;
 	}
 
+      if ((err == GNUTLS_SAN_DNSNAME
+	   || err == GNUTLS_SAN_RFC822NAME
+	   || err == GNUTLS_SAN_URI) &&
+	  strlen (buffer) != size)
+	{
+	  adds (str, _("warning: distributionPoint contains an embedded NUL, "
+		       "replacing with '!'\n"));
+	  while (strlen (buffer) < size)
+	    buffer[strlen (buffer)] = '!';
+	}
+
       switch (err)
 	{
 	case GNUTLS_SAN_DNSNAME:
@@ -552,6 +563,17 @@
 	  return;
 	}
 
+      if ((err == GNUTLS_SAN_DNSNAME
+	   || err == GNUTLS_SAN_RFC822NAME
+	   || err == GNUTLS_SAN_URI) &&
+	  strlen (buffer) != size)
+	{
+	  adds (str, _("warning: SAN contains an embedded NUL, "
+		       "replacing with '!'\n"));
+	  while (strlen (buffer) < size)
+	    buffer[strlen (buffer)] = '!';
+	}
+
       switch (err)
 	{
 	case GNUTLS_SAN_DNSNAME:
@@ -623,8 +645,18 @@
 	      }
 
 	    if (err == GNUTLS_SAN_OTHERNAME_XMPP)
-	      addf (str, _("%s\t\t\tXMPP Address: %.*s\n"), prefix,
-		    (int) size, buffer);
+	      {
+		if (strlen (buffer) != size)
+		  {
+		    adds (str, _("warning: SAN contains an embedded NUL, "
+				 "replacing with '!'\n"));
+		    while (strlen (buffer) < size)
+		      buffer[strlen (buffer)] = '!';
+		  }
+
+		addf (str, _("%s\t\t\tXMPP Address: %.*s\n"), prefix,
+		      (int) size, buffer);
+	      }
 	    else
 	      {
 		addf (str, _("%s\t\t\totherName OID: %.*s\n"), prefix,


PATCH 2

diff -ur gnutls-2.8.1.orig/lib/gnutls_str.c gnutls-2.8.1/lib/gnutls_str.c
--- gnutls-2.8.1.orig/lib/gnutls_str.c	2009-06-02 20:59:32.000000000 +0200
+++ gnutls-2.8.1/lib/gnutls_str.c	2009-08-14 11:40:45.000000000 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2002, 2004, 2005, 2007, 2008  Free Software Foundation
+ * Copyright (C) 2002, 2004, 2005, 2007, 2008, 2009  Free Software Foundation
  *
  * Author: Nikos Mavrogiannopoulos
  *
@@ -363,17 +363,22 @@
 
 /* compare hostname against certificate, taking account of wildcards
  * return 1 on success or 0 on error
+ *
+ * note: certnamesize is required as X509 certs can contain embedded NULs in
+ * the strings such as CN or subjectAltName
  */
 int
-_gnutls_hostname_compare (const char *certname, const char *hostname)
+_gnutls_hostname_compare (const char *certname,
+			  size_t certnamesize,
+			  const char *hostname)
 {
   /* find the first different character */
   for (; *certname && *hostname && toupper (*certname) == toupper (*hostname);
-       certname++, hostname++)
+       certname++, hostname++, certnamesize--)
     ;
 
   /* the strings are the same */
-  if (strlen (certname) == 0 && strlen (hostname) == 0)
+  if (certnamesize == 0 && *hostname == '\0')
     return 1;
 
   if (*certname == '*')
@@ -381,15 +386,16 @@
       /* a wildcard certificate */
 
       certname++;
+      certnamesize--;
 
       while (1)
 	{
 	  /* Use a recursive call to allow multiple wildcards */
-	  if (_gnutls_hostname_compare (certname, hostname))
-	    {
-	      return 1;
-	    }
-	  /* wildcards are only allowed to match a single domain component or component fragment */
+	  if (_gnutls_hostname_compare (certname, certnamesize, hostname))
+	    return 1;
+
+	  /* wildcards are only allowed to match a single domain
+	     component or component fragment */
 	  if (*hostname == '\0' || *hostname == '.')
 	    break;
 	  hostname++;
diff -ur gnutls-2.8.1.orig/lib/gnutls_str.h gnutls-2.8.1/lib/gnutls_str.h
--- gnutls-2.8.1.orig/lib/gnutls_str.h	2009-06-02 20:59:32.000000000 +0200
+++ gnutls-2.8.1/lib/gnutls_str.h	2009-08-14 11:40:45.000000000 +0200
@@ -79,7 +79,7 @@
 int _gnutls_hex2bin (const opaque * hex_data, int hex_size, opaque * bin_data,
 		     size_t * bin_size);
 
-int _gnutls_hostname_compare (const char *certname, const char *hostname);
+int _gnutls_hostname_compare (const char *certname, size_t certnamesize, const char *hostname);
 #define MAX_CN 256
 
 #endif
diff -ur gnutls-2.8.1.orig/lib/openpgp/pgp.c gnutls-2.8.1/lib/openpgp/pgp.c
--- gnutls-2.8.1.orig/lib/openpgp/pgp.c	2009-06-02 20:59:32.000000000 +0200
+++ gnutls-2.8.1/lib/openpgp/pgp.c	2009-08-14 11:40:45.000000000 +0200
@@ -570,7 +570,7 @@
 
       if (ret == 0)
 	{
-	  if (_gnutls_hostname_compare (dnsname, hostname))
+	  if (_gnutls_hostname_compare (dnsname, dnsnamesize, hostname))
 	    return 1;
 	}
     }
diff -ur gnutls-2.8.1.orig/lib/x509/rfc2818_hostname.c gnutls-2.8.1/lib/x509/rfc2818_hostname.c
--- gnutls-2.8.1.orig/lib/x509/rfc2818_hostname.c	2009-06-02 20:59:32.000000000 +0200
+++ gnutls-2.8.1/lib/x509/rfc2818_hostname.c	2009-08-14 11:40:45.000000000 +0200
@@ -74,7 +74,7 @@
       if (ret == GNUTLS_SAN_DNSNAME)
 	{
 	  found_dnsname = 1;
-	  if (_gnutls_hostname_compare (dnsname, hostname))
+	  if (_gnutls_hostname_compare (dnsname, dnsnamesize, hostname))
 	    {
 	      return 1;
 	    }
@@ -84,7 +84,7 @@
 	  found_dnsname = 1;	/* RFC 2818 is unclear whether the CN
 				   should be compared for IP addresses
 				   too, but we won't do it.  */
-	  if (_gnutls_hostname_compare (dnsname, hostname))
+	  if (_gnutls_hostname_compare (dnsname, dnsnamesize, hostname))
 	    {
 	      return 1;
 	    }
@@ -104,7 +104,7 @@
 	  return 0;
 	}
 
-      if (_gnutls_hostname_compare (dnsname, hostname))
+      if (_gnutls_hostname_compare (dnsname, dnsnamesize, hostname))
 	{
 	  return 1;
 	}
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 419 bytes
Desc: not available
URL: </pipermail/attachments/20090814/7f81d2ea/attachment.pgp>


More information about the Gnutls-devel mailing list