Back to Posts

Share this post

GHOST, Analisi Tecnica

Posted by: voidsec

Reading Time: 6 minutes

GHOST è una grave vulnerabilità presente nella libreria glibc in Linux e consente agli attaccanti di eseguire codice remoto (RCE – Remote Code Execution) senza avere alcuna conoscenza preliminare delle credenziali del sistema. A questa vulnerabilità è stato assegnato il CVE-2015-0235.

La vulnerabilità è stata scoperta dai ricercatori di Qualys e colpisce la libreria dalla versione 2.2, inclusa nei sistemi Linux da Novembre dell’anno 2000.

Il bug è stato corretto il 21 Maggio 2013 (tra le release 2.17 e 2.18), purtroppo però la falla non è stata riconosciuta come una minaccia per la sicurezza e, di conseguenza, la maggior parte delle distribuzioni stabili, con supporto a lungo termine, sono rimaste esposte; Debian 7 (wheezy), Red Hat Enterprise Linux 6 & 7, CentOS 6 & 7, Ubuntu 12.04, sono solo alcuni degli esempi.

Che cos’è la libreria glibc?

Glibc è un’implementazione della libreria standard C e parte fondamentale del sistema operativo Linux.

Analisi tecnica della vulnerabilità GHOST

GHOST è una vulnerabilità di tipo buffer overflow innescata dalla chiamata gethostbyname(). Questa funzione viene utilizzata per risolvere i nomi di dominio in indirizzi IP (DNS) da varie applicazioni.

Attraverso la funzione gethostbyname() o gethostbyname2(), l’overflow èpresente nell’heap; mentre nelle funzioni gethostbyname_r() o gethostbyname2_r(), l’overflow è definito dal “chiamante” (e può essere localizzato nell’heap, nello stack, .data, .bss, ecc; ma in pratica non sono state osservate chiamate di questo genere).

L’overflow è lungo al massimo la dimensione di, sizeof(char *), 4 byte per macchine a 32-bit e 8 byte su macchine a 64-bit; composto solo di numeri (‘0’…’9’), punti (‘.’), e carattere nullo (‘\0’); nonostante queste limitazioni, l’esecuzione di codice arbitrario può comunque essere raggiunta.

La funzione vulnerabile, __nss_hostname_digits_dots(), è chiamata internamente dalla glibc in nss/getXXbyYY.c e in nss/getXXbyYY_r.c.
La chiamata è “protetta” dalla macro #ifdef HANDLE_DIGITS_DOTS, definita in:

  • inet/gethstbynm.c
  • inet/gethstbynm2.c
  • inet/gethstbynm_r.c
  • inet/gethstbynm2_r.c
  • nscd/gethstbynm3_r.c

Questi file implementano gethostbyname() e sono quindi l’unico modo per raggiungere __nss_hostname_digits_dots() e il buffer overflow.

Lo scopo di questa funzione è di evitare dispendiose ricerche DNS se l’argomento hostname è già un indirizzo IPv4 o IPv6.

Codice tratto dalla libreria glibc-2.17:

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 == '\0')
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 }
...
  • Linee 85-86 calcola size_needed per memorizzare tre distinte entità nel buffer:
    host_addr, h_addr_ptrs, e name (l’hostname).
  • Linee 88-117 si assicurano che il buffer sia sufficientemente grande.
  • Lines 121-125 prepara il puntatore per memorizzare quattro entità nel buffer: host_addr, h_addr_ptrs, h_alias_ptr e hostname. sizeof(*h_alias_ptr), la dimensione del puntatore a carattere, manca nel calcolo size_needed.

La strcpy() a linea 157 ci permette di scrivere oltre la fine del buffer, (dipende da strlen(name) e dall’allineamento) 4 byte o 8 byte in base all’architettura.

Per raggiungere l’overflow a linea 157 l’argomento hostname deve soddisfare i seguenti requisiti:

  • Il suo primo carattere deve essere un numero (linea 127).
  • L’ultimo carattere non deve essere un punto (linea 135).
  • Deve comprendere solo numeri e punti (linea 197).
  • Deve essere abbastanza “lungo” da sovrascrivere il buffer. Per esempio la funzione gethostbyname() inizialmente alloca il suo buffer con una chiamata a malloc(1024)(1KB).
  • Deve essere “parsato” correttamente come indirizzo IPv4 da inet_aton() (linea 143), o come indirizzo IPv6 inet_pton() (linea 147); inet_aton() è però l’unica opzione per raggiungere l’overflow, e l’hostname deve essere in una delle seguenti forme: “a.b.c.d“, “a.b.c“, “a.b“, o “a“; dove: a, b, c, d devono essere interi senza segno, convertiti correttamente in decimali o ottali (non in esadecimale, perché ‘x‘ e ‘X‘ sono proibiti).

In conclusione, se il parametro name contiene un indirizzo IP nella funzione __nss_hostname_digits_dots() l’IP viene scritto all’interno di un buffer; la posizione in cui verrà scritto dipende dalla lunghezza del parametro name passatogli. Pertanto passandogli come parametro un indirizzo IP più lungo del normale (1024.10224.102224.1022224) si forzerà la funzione a scrivere l’indirizzo “un byte più a destra”, permettendo così che un Byte venga scritto fuori dal buffer.


Fortunatamente per la maggior parte degli utenti, ci sono molti fattori che mitigano i rischi di GHOST, un esame più attento rivela che questa particolare vulnerabilità, seppur seria, non è facilmente sfruttabile, con solo quattro o otto byte come vettore iniziale dell’exploit, guadagnare un ulteriore accesso è questione fortemente dipendente dalla progettazione dell’applicazione e dell’utilizzo della memoria.

Inoltre, le funzioni oggetto di questa vulnerabilità sono obsolete, esse non possono essere utilizzate per tradurre i nomi di dominio in indirizzi IPv6 e le applicazioni più recenti utilizzano la funzione getaddrinfo(), con il supporto IPv6.

Il rischio di exploit effettivi per GHOST è relativamente modesto rispetto a vulnerabilità come Shellshock, Heartbleed o POODLE.

UPDATE 01/02/2015

Il CMS WordPress risulta vulnerabile a GHOST, in quanto la funzione wp_http_validate_url() usata per convalidare gli URL pingback, utilizza gethostbyname().

Sucuri-GetHostbyName-GHOST-flaw

Raccomandiamo pertanto di disabilitare XML-RPC e le richieste di Pingback per ridurre la superficie d’attacco.

Gli amministratori possono eseguire il seguente codice sui loro sistemi per scoprire se sono interessati dalla vulnerabilità GHOST:

php -r '$e="0?;for($i=0;$i<2500;$i++){$e="0$e";}
gethostbyname($e);' Segmentation fault

di VoidSec e kalup

Back to Posts