glibc-ghost

Como utilizar o getaddrinfo para evitar a falha GHOST

Em janeiro de 2015 se ouviu falar de uma falha de segurança na glibc chamada GHOST. Esta falha poderia ser explorada por um programa malicioso utilizando as funções gethostbyname() e gethostbyname2() da glibc. O ataque acontece por um buffer overflow dentro destas funções quando um endereço inválido é passando como parâmetro. Ao explorar esta falha, o atacante consegue executar códigos arbitrários com as permissões do usuários executando o processo.

Este problema existia na glibc desde 2000, e foi corrigido em 2013, mas como a falha não havia sido detectada, não foi criada outra versão estável da glibc após a correção. Quando a falha foi exposta, grandes distribuições como Red Hat e Debian atualizaram a sua versão da glibc. Segundo esta publicação da Cisco, a falha não é tão assutadora quanto parece, pois o software a ser explorado precisa aceitar hostnames como entrada e ainda assim existem algumas situações em que o bug pode ser explorado: o primeiro carácter precisa ser um dígito, o último carácter não pode ser um ponto e o hostname precisa ser construído somente por dígitos e pontos (um endereço IPv4 comum).

As funções gethostbyname e gethostbyname2 foram criadas nos anos 80 e estão depreciadas a algum tempo, podendo até ser noticiadas pela leitura do man. A alternativa é utilizar a função getaddrinfo, que além de ser a função substituta de gethostbyname*(), trabalha de forma transparente com IPv6.

Veja abaixo um exemplo da utilização do gethostbyname2:


Segue a saída do programa:

[marcos@xfiles ghost]$ ./gethostbyname facebook.com
Host: facebook.com
IPv4: 173.252.120.68
Host: facebook.com
IPv6: 2a03:2880:2130:cf24:face:b00c:0:25de

A chamada gethosbyname2 retorna um ponteiro para struct hostent. Esta struct contém um campo chamado h_addr_list, onde estão todos os endereços IP relacionados ao hostname informado. O campo h_addrtype informa se o IP retornado é IPv4 (AF_INET) ou IPv6 (AF_INET6). Após verificar se o retorno é um IPv4 ou IPv6 é necessário decodificar o IP, que está em formato binário. A chamada inet_ntop recebe como parâmetro uma struct in_addr ou struct in6_addr e retorna o IP em forma de string. Neste exemplo foi utilizado um ponteiro void para armazenar as structs afim de simplificar o código, já que o segundo parâmetro da função inet_ntop espera um void *.

A chamada gethostbyname2 foi utilizada pois gethostbyname não retornou nenhum endereço IPv6, embora as man pages digam que ela retorna.

A função getaddrinfo trabalha da mesma forma que a gethostbyname*, mas esta é mais transparente em relação a filtragem dos hosts retornados. Primeiramente vamos a um exemplo:

Segue abaixo a saída deste programa executado:

[marcos@xfiles ghost]$ ./getaddrinfo facebook.com
Host: facebook.com
IPv4: 173.252.120.68
IPv6: 2a03:2880:2130:cf24:face:b00c:0:25de

A chamada getaddrinfo recebe quatro parâmetros:

  • um nó (hostname)
  • um serviço (porta)
  • uma struct addinfo, ou hints
  • e um ponteiro para addrinfo, que irá ser conter os IPs do hostname

O hostname é basicamente o host que queremos buscar, ou seja, é o mesmo parâmetro da chamada gethostbyname. Já o serviço especificado pode estar em formato decimal, como uma porta, ou um serviço como “http”. Podemos deixar este campo como NULL. A struct hints funciona como um filtro, informando qual o tipo de endereços que desejamos receber da chamada getaddrinfo para o host informado. Neste caso nós informamos ai_family como AF_UNSPEC, pois queremos receber tanto endereços IPv4 quanto IPv6 (para buscar por somente um deles basta informar AF_INET ou AF_INET6), ai_socktype como 0, pois esperamos tanto por TCP quanto UDP, e ai_flags como AI_CANONNAME para que o hostname descoberto seja retornado na struct addrinfo. Já o quarto parâmetro é um ponteiro para addrinfo que irá conter todos os endereços IP relacionados ao host informado.

A chamada getaddrinfo retorna zero em caso de sucesso, ou um código de erro em caso de falha. O erro em sua forma descritiva por ser visto pela chamada gai_strerror, que recebe o retorno de getaddrinfo.

Após executado o getaddrinfo, então iteramos sobre todos os IPs retornados. O campo ai_next sempre aponta para a próxima struct addrinfo. O processo para imprimir o IP do host é basicamente o mesmo do gethostbyname. freeaddrinfo server para liberar a memória alocada da chamada getaddrinfo.

Como podemos observar, adaptar o código para getaddrinfo não é difícil, muito pelo contrário, e seguir as recomendações do man parecem ser sempre algo bom a se fazer!

Até a próxima!

Referências:

Red Hat CVE
GHOST: glibc vulnerability (CVE-2015-0235)
GHOST, a critical Linux security hole, is revealed
Qualys Security Advisory CVE-2015-0235 – GHOST: glibc gethostbyname buffer overflow
You shouldn’t be using gethostbyname() anyway
Man inet_pton
Man gethostbyname
Man getaddrinfo

  • Lukas Silva

    perfeito