GHOST, una vulnerabilidad de Linux superada

Publicado por Diego Córdoba en

Hoy analizaremos las causas y algunos detalles técnicos de Ghost, una vulnerabilidad que afecta a algunas funciones de gestión de sockets y comunicaciones en versiones de la biblioteca estándar de C en Linux (glibc) anteriores a la 2.18.


Las versiones de las bibliotecas glibc (GNU C Library) anteriores a la versión 2.18 están afectadas por la vulnerabilidad GHOST, una vulnerabilidad de ejecución remota de código.

La vulnerabilidad radica en realidad en la función gethostbyname() , que varios hemos utilizado para programar sockets Unix, y de ella también deriva su nombre.ghost infosec vulnerability qualsys sec hac

GHOST fue descubierta por investigadores de Qualys, durante una auditoría descubrieron un buffer overflow en la función interna the_nss_hostname_digits_dots()  de la biblioteca glibc, y es explotable por medio del uso de las funciones gethostbyname*, como la propia gethostbyname(), gethostbyname2(), gethostbyname_r() y gethostbyname2_r(). Podemos ver el post original en el blog de Qualys.

La solución a la vulnerabilidad fue realizada en mayo del 2013 con la actualización a la versión glib-2.18.

Qualys también provee detalles sobre un exploit en el servidor de correos Exim, pero también fueron expuestos MySQL server, Apache, Cups, Dovecot, servidores SSH y varios más, puesto que los servicios de red generalmente hacen uso de gethostbyname() para establecer sus sockets.

La mayoría de las distribuciones liberaron parches para la vulnerabilidad, y hoy en día prácticamente todos los sistemas Linux están protegidos contra GHOST.

Detalles técnicos de Ghost

Analicemos el siguiente código:

 35 int
 36 __nss_hostname_digits_dots (const char *name, struct hostent *resbuf,
 37                             char **buffer, size_t *buffer_size,
 38                             size_t buflen, struct hostent **result,
 39                             enum nss_status *status, int af, int *h_errnop)
 40 {
 ..
 57   if (isdigit (name[0]) || isxdigit (name[0]) || name[0] == ':')
 58     {
 59       const char *cp;
 60       char *hostname;
 61       typedef unsigned char host_addr_t[16];
 62       host_addr_t *host_addr;
 63       typedef char *host_addr_list_t[2];
 64       host_addr_list_t *h_addr_ptrs;
 65       char **h_alias_ptr;
 66       size_t size_needed;
 ..
 85       size_needed = (sizeof (*host_addr)
 86                      + sizeof (*h_addr_ptrs) + strlen (name) + 1);
 87
 88       if (buffer_size == NULL)
 89         {
 90           if (buflen < size_needed)
 91             {
 ..
 95               goto done;
 96             }
 97         }
 98       else if (buffer_size != NULL && *buffer_size < size_needed)
 99         {
100           char *new_buf;
101           *buffer_size = size_needed;
102           new_buf = (char *) realloc (*buffer, *buffer_size);
103
104           if (new_buf == NULL)
105             {
...
114               goto done;
115             }
116           *buffer = new_buf;
117         }
...
121       host_addr = (host_addr_t *) *buffer;
122       h_addr_ptrs = (host_addr_list_t *)
123         ((char *) host_addr + sizeof (*host_addr));
124       h_alias_ptr = (char **) ((char *) h_addr_ptrs + sizeof (*h_addr_ptrs));
125       hostname = (char *) h_alias_ptr + sizeof (*h_alias_ptr);
126
127       if (isdigit (name[0]))
128         {
129           for (cp = name;; ++cp)
130             {
131               if (*cp == '�')
132                 {
133                   int ok;
134
135                   if (*--cp == '.')
136                     break;
...
142                   if (af == AF_INET)
143                     ok = __inet_aton (name, (struct in_addr *) host_addr);
144                   else
145                     {
146                       assert (af == AF_INET6);
147                       ok = inet_pton (af, name, host_addr) > 0;
148                     }
149                   if (! ok)
150                     {
...
154                       goto done;
155                     }
156
157                   resbuf->h_name = strcpy (hostname, name);
...
194                   goto done;
195                 }
196
197               if (!isdigit (*cp) && *cp != '.')
198                 break;
199             }
200         }
...

Las líneas 85 y 86 calculan el tamaño necesario para almacenar 3 entidades en un buffer: host_addr, h_addr_ptrs y name (el hostname). Laslineas 88-117 verifican la longitud de dicho buffer.
Las líneas 121-125 preparan punteros para almacenar 4 entidades: host_addr, h_addr_ptrs, h_alias_ptr y hostname. Vemos que el tamaño del puntero *h_alias_ptr no estaba incluido en el tamaño del buffer anterior.

La función strcpy() de la línea 157 debería permitirnos escribir pasado el final del buffer, al menos (dependiendo del strlen(name))4 bytes en arquitecturas de 32b, y 64b en arquitecturas de 64b.

Para lograr el buffer overflow en la línea 157, el hostname debe cumplir los siguientes requisitos:

  • Su primer caracter debe ser un dígito (segun la línea 127).
  • Su último caracter no debe ser un punto (segun la línea 135).
  • Solo debe componerse de puntos y números (ver línea 197).
  • Debe ser parseado como una dirección IPv4 válida (ver línea 143, función inet_aton()).
  • No se tienen en cuenta las direcciones IPv6 válidas (ver línea 147, función inet_pton()), puesto que una dirección ipv6 requiere introducir el caracter «:», que no satisface la premisa anterior.

Es por esto que la función inet_aton() es la única que debe verificar un hostname válido como dirección ipv4, y ésta debe ser de la forma «a.b.c.d», o «a.b.c», o «a.b», o «a», donde a, b c y d deben ser de tipo «unsigned int».

Verificando si nuestro sistema es vulnerable

Es muy dificil que nuestro sistema sea vulnerable puesto que la versión vulnerable de glibc data del 2013, pero nunca está de mas verificarlo.

Para ello, basta con descargarse un código C que verifica la vulnerabilidad en nuestro sistema. Descargamos el código, lo compilamos, y lo ejecutamos:

cd /tmp/
diego@sol:/tmp$ wget https://webshare.uchicago.edu/orgs/ITServices/itsec/Downloads/GHOST.c
--2015-02-01 01:34:37--  https://webshare.uchicago.edu/orgs/ITServices/itsec/Downloads/GHOST.c
Resolving webshare.uchicago.edu (webshare.uchicago.edu)... 128.135.22.61
Connecting to webshare.uchicago.edu (webshare.uchicago.edu)|128.135.22.61|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1046 (1.0K) [text/x-csrc]
Saving to: ‘GHOST.c’

GHOST.c                   100%[=====================================>]   1.02K  --.-KB/s   in 0s    

2015-02-01 01:34:39 (30.5 MB/s) - ‘GHOST.c’ saved [1046/1046]

diego@sol:/tmp$ gcc GHOST.c
diego@sol:/tmp$ ./a.out
not vulnerable
diego@sol:/tmp$ :-)

Otro método… como sabemos que la versión de glibc anterior a la 2.18 es vulnerable, solo debemos ejecutar el siguiente comando y verificar la versión de nuestra biblioteca:

diego@sol:~$ ldd --version
ldd (Debian GLIBC 2.19-13) 2.19
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.

Como vemos, en mi caso (Debian testing «Jessie» con kernel 3.16.0-4-amd64) posee la versión 2.19-13.

Conclusiones

A no desesperarse, que hay mucho ruido en la red con cada vulnerabilidad en bibliotecas del kernel Linux, pero no todas representan una gravedad extrema, por lo menos ahora, y no por esto Linux es un mal sistema operativo. Es bueno que las vulnerabilidades se hagan publicas para forzar su corrección, puesto que de lo contrario podría ser descubierta por programadores inescrupulosos que la exploten para beneficio propio.

Fuentes:
http://www.openwall.com/lists/oss-security/2015/01/27/9
http://www.cyberciti.biz/faq/cve-2015-0235-ghost-glibc-buffer-overflow-linux-test-program/


Diego Córdoba

- Ingeniero en Informática - Mg. Teleinformática - Tesis pendiente - Docente universitario - Investigador