
Prova de conceito de exploit para CVE-2020-12124 direcionado ao roteador Wavlink AC1200, demonstrando injeção de comandos não autenticada e estouro de buffer de pilha nas interfaces CGI.
originalmente em https://www.klogixsecurity.com/scorpion-labs-blog/anatomy-of-an-iot-exploit-from-hands-on-to-rce
por David E. Baker, publicado em 1 de junho de 2023
Este estudo trata do firmware do roteador Gigabit Wavlink Wireless-AC1200 de junho de 2020. As vulnerabilidades aqui discutidas podem ou não ter sido corrigidas pelo fabricante, mas este é um caso de pesquisa de vulnerabilidade que mostrará a jornada como recompensa, e não sua conclusão. O autor conduziu esta pesquisa antes da divulgação pública das vulnerabilidades, mas depois de elas terem sido descobertas independentemente por outros pesquisadores e reportadas ao fabricante.
O fabricante disponibiliza firmware para seus produtos na seção de suporte de seu site; esta é uma forma comum de obter firmware de IoT e uma alternativa útil para extraí-lo da memória do dispositivo. O firmware não é criptografado, portanto pode ser facilmente extraído com binwalk. A análise dinâmica foi feita com acesso a um exemplar físico do dispositivo, e a análise estática foi feita através do Ghidra.
A interface web do roteador Gigabit Wavlink Wireless-AC1200 possui vários endpoints vulneráveis que permitem a cópia irrestrita de dados fornecidos pelo usuário para a pilha da aplicação ou até mesmo diretamente para a linha de comando, resultando em execução arbitrária de comandos.
As varreduras iniciais do dispositivo sugerem que o único recurso exposto era o console administrativo web, acessível a usuários autenticados na interface LAN via HTTP na porta TCP 80. O dispositivo pode oferecer mais serviços, mas estes não são ativados de fábrica e por padrão. Dessa forma, esta investigação foca apenas na interface web.
Uma varredura nmap do exemplar do dispositivo, mostrando apenas a interface
web ouvindo.
Os testes habituais — como as injeções de comando típicas encontradas em
painéis de diagnóstico de dispositivos que permitem uma injeção de comando nos
parâmetros de um comando ping ou traceroute — não produziram resultados
imediatamente interessantes, o que foi decepcionante.
Opções de gerenciamento disponíveis após autenticação no
painel administrativo web. "Armazenamento USB" pode ser visto como a segunda opção.
A primeira interface (explorável em última análise) examinada aqui foi encontrada no painel "Armazenamento USB", que pode ser visto como a segunda opção na captura de tela acima. O dispositivo possui uma porta USB adjacente aos seus conectores Ethernet 802.2, sugerindo que poderia oferecer funcionalidade de armazenamento conectado à rede (NAS).
Uma fotografia da parte traseira do exemplar real, mostrando a
disponibilidade USB.
Um princípio simples na pesquisa de vulnerabilidades é que, quanto mais componentes um trecho de código interage e mais partes móveis possui, maior a probabilidade de haver código explorável por perto. A presença de capacidades NAS é promissora porque indica a presença de código que interage simultaneamente com a camada de software do dispositivo, camada de hardware e periferia conectada (o próprio armazenamento USB).
A interface de gerenciamento para o console de Armazenamento USB é mostrada abaixo.
A presença apenas do campo "Workgroup" é promissora, pois sugeriria
que este roteador WiFi pode até tentar interagir via Server Message Block
(SMB) — uma grande capacidade para um roteador IoT. Não consigo contar o número
de vezes que vi entrada fornecida pelo usuário ser enviada diretamente para a linha de comando
como argumento para a função Unix smbpasswd.
Opções de armazenamento USB disponíveis para usuários autenticados.
Tentativas iniciais de manipular essas configurações falharam porque o dispositivo não detectou um drive USB, como visto abaixo.
As alterações de configuração nas opções de Armazenamento USB não serão
salvas a menos que um drive formatado adequadamente seja conectado manualmente à
porta USB do dispositivo.
No entanto, assim que um drive formatado corretamente é conectado, o dispositivo permitiu que um nome de usuário e senha FTP fossem definidos. Como suspeitado, ele colocou essa entrada fornecida pelo usuário na linha de comando:
Uma injeção de comando no campo 'password' concede o primeiro
acesso ao shell diretamente ao sistema operacional do dispositivo.
Embora interessante, essa vulnerabilidade é difícil de gerar entusiasmo esmagador: ela exige não apenas acesso autenticado à interface administrativa do dispositivo, mas também acesso físico ao dispositivo para manipular seu drive USB. A exploração acima permite que um pesquisador interaja com os componentes individuais do sistema operacional (e os exfiltre para fins de engenharia reversa).
O dispositivo era um sistema Linux centrado em busybox com uma interface web
alimentada por Lighttpd. A funcionalidade Common Gateway Interface (CGI)
era fornecida por binários individuais em /etc_ro/lighttpd/www/cgi-bin/,
com requisições web para URIs CGI lançando esses binários diretamente. Uma rápida
olhada em nas.cgi no Ghidra mostra a injeção de comando na linha 38 abaixo,
que envia uma senha fornecida pelo usuário diretamente para a função do_system
(ela própria simplesmente um wrapper em torno da chamada de sistema libc padrão).
A entrada do usuário é colocada na linha de comando como argumento para
o script chpasswd.sh na Linha 38, resultando em uma injeção de comando
e acesso ao shell diretamente ao sistema operacional do dispositivo.
Olhando pelo diretório /cgi-bin/, reduz-se a tarefa de encontrar uma
exploração mais interessante a enumerar as interfaces CGI disponíveis para o
usuário, mostradas abaixo:
Uma lista exaustiva dos binários CGI disponibilizados no
dispositivo, obtida ao vivo do shell estabelecido pela exploração descrita nesta
seção. A entrada do usuário é colocada na linha de comando como argumento para
o script chpasswd.sh na Linha 38, resultando em uma injeção de comando
e acesso ao shell diretamente ao sistema operacional do dispositivo.
Várias coisas se destacam no exame inicial. A primeira coisa importante
a notar é que os binários CGI frequentemente chamam a função check_valid_user.
Este método verifica se o endereço IP que está fazendo a requisição está
armazenado em um arquivo temporário específico no sistema de arquivos. Testes
mínimos mostram que o status de autenticação de um cliente não é verificado até que uma chamada a
este método tenha sido feita, então toda a superfície de código em cada binário
CGI antes da invocação desta função é acessível sem autenticação.
A descompilação de adm.cgi mostrando o método check_valid_user.
Todo o código anterior a esta chamada é executado antes de verificar o
status de autenticação do solicitante.
Outra observação interessante é o grande número de métodos que copiam
a entrada fornecida pelo usuário diretamente para a pilha. Por exemplo, a
descompilação de wireless.cgi mostra o argumento do parâmetro NewName obtido
do corpo de uma requisição web nas Linhas 14 e 15, seguido por um
strcpy sem proteção desta entrada do usuário para a pilha na Linha 34, mostrado abaixo:
As linhas 14 e 34 demonstram um strcpy sem proteção do parâmetro NewName
do corpo da requisição diretamente para a pilha do programa. A
descompilação de adm.cgi mostrando o método check_valid_user. Todo o
código anterior a esta chamada é executado antes de verificar o
status de autenticação do cliente.
Esta cópia da entrada do usuário para a pilha por si só sugere uma exploração de corrupção de memória, que o seguinte comando pode verificar:
curl –XPOST --data "page=SetName&NewName=\`python3 –c 'print(\\"A\\"\*(512)'\`" http://target-ip/cgi-bin/wireless.cgi
Embora promissor, um ataque orientado a retorno não é ideal aqui. Usando o acesso ao shell do sistema operacional já obtido, o seguinte comando exibe '1', mostrando que a randomização do layout do espaço de endereço (ASLR) está fracamente aplicada, deixando uma cadeia ROP com 1 chance em 256 de acertar o gadget desejado:
# cat /proc/sys/kernel/randomize\_va\_space
1
Além disso, o comando abaixo mostra que os binários little-endian são compilados com o byte de proteção de pilha ativado, então apenas o último gadget em uma cadeia pode pousar em um local pré-determinado dentro do binário. Se um processo watchdog estiver presente para reiniciar o servidor web após uma falha, então não é impossível lançar repetidamente uma exploração orientada a retorno e esperar sucesso eventualmente, mas também é possível que existam bugs melhores escondidos em outras partes do código.
xxd /etc\_ro/lighttpd/www/cgi-bin/wireless.cgi | head -n 10
Continuando a examinar as funções CGI, eventualmente se examinará
live\_api.cgi. Este binário não faz nenhuma chamada a check\_valid\_user, então
quaisquer requisições web para a URI /cgi-bin/live\_api.cgi executam esta aplicação CGI
sem autenticação. A Linha 9 na Figura 11 mostra que a variável de ambiente
QUERY\_STRING, que (de acordo com a especificação Apache CGI) é
a parte da URI da requisição imediatamente após um ponto de interrogação e,
portanto, fornecida pelo usuário, é armazenada em pcVar1 e na Linha 19 da Figura 11
é enviada ao método satellite_status.
Uma descompilação de live\_api.cgi mostrando a entrada do usuário obtida
da URI na Linha 9 e sendo enviada para satellite\_status na Linha 19. A
descompilação de adm.cgi mostrando o método check\_valid\_user. Todo o
código anterior a esta chamada é executado antes de verificar o
status de autenticação do cliente.
A descompilação do método satellite_status, mostrada na Figura 12, mostra
que a própria string de consulta (agora param\_1) é analisada em busca dos
parâmetros page, id e ip. O parâmetro ip é copiado via função
sprintf para uma variável local na Linha 38 da Figura 12 e, na Linha 39, é
passado para a função do\_system. A ausência de uma chamada a
check\_user\_auth indica que a entrada arbitrária do cliente de um
usuário não autenticado na URI será colocada diretamente na linha de comando
no parâmetro ip da URI, confirmado a seguir:
Uma descompilação da função satellite\_status, que mostra
a string de consulta (agora param\_1) analisada em busca do parâmetro ip nas
Linhas 22 e 23, e então uma chamada a do\_system nas linhas 38 e 39.
A prova está no pudim, mostrando a exploração sendo usada para
efetuar uma tomada remota do dispositivo.
Encontrar explorações em um dispositivo IoT recém-lançado no mercado pode parecer fruta fácil, como foi mencionado no início deste post, mas o valor desta investigação esteve em sua jornada, e não em seu destino.
Uma compreensão da circunvenção da autenticação e da localização da injeção de comando teria sido improvável, ou mesmo impossível, sem a análise estática do firmware. Se não estivesse disponível online, teria exigido acesso dinâmico ao sistema operacional do dispositivo para obtê-los, para o qual haveria poucas esperanças de acesso dinâmico ao dispositivo. Este nível de acesso, por si só, dependia tanto de acesso físico ao dispositivo quanto de hardware especializado ou de um palpite orientado sobre os locais prováveis no código onde erros poderiam ter sido cometidos.
Esperamos que você tenha gostado desta jornada e que volte para mais. Boas hackeagens!
Autor:
David Baker, Consultor Sênior de Segurança, Testes, K logix