GHOST, Analisi Tecnica
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().
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