
Este repositório contém um post de blog sobre minha análise do CVE-2018-19987, uma injeção autenticada de comandos do SO que afeta vários roteadores D-Link.
Olá!
Neste pequeno post do blog, vou detalhar uma análise rápida que realizei para entender melhor os detalhes por trás do CVE-2018-19987. No começo, procurei esse CVE porque pensei que não houvesse nenhuma informação pública ou exploit. Pouco depois de fazer uma primeira análise, encontrei dois PoCs públicos no GitHub que você pode encontrar aqui e aqui.
Como já tinha feito a maior parte do trabalho e não tinha encontrado uma análise completa, decidi escrever o post de qualquer forma.
Espero que goste!
Atualização: Bem... depois de pesquisar no Google enquanto estava preso neste post do blog, encontrei o seguinte artigo que descreve uma vulnerabilidade muito semelhante (CVE-2018-19986) no D-Link DIR-818. Análise de vulnerabilidade de roteador série (5): Análise e reprodução de injeção de comando CVE-2018-19986 DIR-818LW&828 (traduzido pelo Google) por Ogur1.
Comecei minha análise reunindo todas as informações que pude sobre o dispositivo:
Nota: realizei toda a análise com o firmware v3.01B02.
As informações públicas para esta vulnerabilidade, disponíveis na página do Mitre, forneceram informações suficientes para começar:
dispositivos D-Link DIR-822 Rev.B 202KRb06, DIR-822 Rev.C 3.10B06, DIR-860L Rev.B 2.03.B03, DIR-868L Rev.B 2.05B02, DIR-880L Rev.A 1.20B01_01_i3se_BETA e DIR-890L Rev.A 1.21B02_BETA manipulam incorretamente IsAccessPoint em /HNAP1/SetAccessPointMode. No código-fonte do SetAccessPointMode.php, o parâmetro IsAccessPoint é salvo no arquivo de script ShellPath sem qualquer verificação de regex. Após a execução do arquivo de script, ocorre a injeção de comando. Uma mensagem XML vulnerável do /HNAP1/SetAccessPointMode poderia conter metacaracteres de shell no elemento IsAccessPoint, como a string `telnetd`.
Para iniciar a análise, precisei de algo para examinar, então prossegui para extrair o sistema de arquivos do roteador.
binwalk -eM DIR822C1_FW301WWb02.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 DLOB firmware header, boot partition: "dev=/dev/mtdblock/1"
10380 0x288C LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 4213444 bytes
1376372 0x150074 PackImg section delimiter tag, little endian size: 10505216 bytes; big endian size: 5021696 bytes
1376404 0x150094 Squashfs filesystem, little endian, version 4.0, compression:lzma, size: 5019773 bytes, 2282 inodes, blocksize: 131072 bytes, created: 2016-03-18 09:35:31
Como podemos ver, o Binwalk detecta um sistema de arquivos Linux. Se olharmos mais atentamente as informações que temos, podemos ver que o aviso menciona o caminho /HNAP1. Portanto, podemos começar a examinar como o roteador lida com essas URLs.
Conforme explicado no excelente post do blog /dev/tty0, essas URLs no final são tratadas por um binário chamado cgibin, localizado em htdocs/cgibin. Mas eu queria entender melhor como isso estava configurado e, no final, me levou a ter que entender como o servidor HTTP estava configurado para processar solicitações. Procurei pelo nome do arquivo httpd.conf, assumindo que era usado para configurar o servidor HTTP, e encontrei um arquivo chamado HTTP.php na pasta /etc/services/, contendo, entre outras coisas, as seguintes linhas interessantes:
$httpd_conf = "/var/run/httpd.conf";
fwrite("a",$START, "xmldbc -P /etc/services/HTTP/httpcfg.php > ".$httpd_conf."\n");
fwrite("a",$START, "event PREFWUPDATE add /etc/scripts/prefwupdate.sh\n");
fwrite("a",$START, "httpd -f ".$httpd_conf."\n");
fwrite("a",$START, "event HTTP.UP\n");
fwrite("a",$START, "exit 0\n");
Presumi que em algum momento esse arquivo é executado e escreve o arquivo /var/run/httpd.conf. Para aprender como o servidor web estava configurado, prossegui para analisar o arquivo /etc/services/HTTP/httpcfg.php (incluí apenas as partes relevantes):
if ($hnap > 0)
{
echo
" Control". "\n".
" {". "\n".
" Alias /HNAP1". "\n".
" Location /htdocs/HNAP1". "\n".
" External". "\n".
" {". "\n".
" /usr/sbin/hnap { hnap }". "\n".
" }". "\n".
" IndexNames { index.hnap }". "\n".
" }". "\n";
}
Agora temos todas as peças! Podemos assumir que o servidor web está configurado para lidar com solicitações HTTP para /HNAP1 usando o /usr/sbin/hnap, e com base no post do blog /dev/tty0 sabemos que será um link para o binário htdocs/cgibin.
O próximo passo foi dar uma olhada no binário cgibin para entender como ele lidava com solicitações HNAP. Na imagem a seguir podemos ver parte da função main descompilada, onde o caminho da URL passado para ela é comparado com diferentes caminhos (como session.cgi, authentication.cgi, captcha.cgi, para citar alguns) até que hnap seja encontrado e nossa função hnap_main seja chamada.
nota: Ao analisar a função main, tive alguns problemas para identificar a string hnap, pois o Ghidra não a detectou como string durante a análise inicial.
O próximo passo foi entender o que essa função fazia. Fornecerei apenas trechos de código para as partes relevantes que ajudarão a entender melhor o que a função faz, e também completarei outras partes relevantes com algum pseudocódigo — renomeei algumas variáveis para esclarecer seu propósito.
...
HTTP_SOAPACTION = getenv("HTTP_SOAPACTION");
REQUEST_METHOD = getenv("REQUEST_METHOD");
HNAP_AUTH = getenv("HTTP_HNAP_AUTH");
__haystack = getenv("HTTP_COOKIE");
pcVar1 = getenv("HTTP_REFERER");