
Este é um breve relatório de uma vulnerabilidade (CVE-2019-12489) descoberta ao explorar um modem/roteador Fastgate da Fastweb e o Ghidra. A vulnerabilidade permite a execução de uma injeção de comando por meio de uma requisição http e pode ser usada para habilitar um shell SSH.
Todos os arquivos de firmware estão localizados em http://59.0.121.191:8080/ACS-server/file/FILENAME
Onde FILENAME é igual a:
Então, por exemplo, para obter o último firmware basta ir para http://59.0.121.191:8080/ACS-server/file/0.00.89_FW_200_Askey
Para extrair os arquivos, precisamos do utilitário em https://github.com/nlitsme/ubidump (este foi o primeiro que funcionou para mim).
Executando binwalk no arquivo, obtemos:
$ binwalk 0.00.89_FW_200_Askey
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
94 0x5E JFFS2 filesystem, little endian
3276894 0x32005E UBI erase count header, version: 1, EC: 0x0, VID header offset: 0x800, data offset: 0x1000
Não precisamos do sistema de arquivos JFFS2, apenas da parte UBI. Para isso, podemos cortar o arquivo usando dd.
Para isso, precisamos da dimensão total e do ponto de início da UBI.
A dimensão do arquivo pode ser obtida usando ls:
$ ls -la
total 55836
drwxr-xr-x 3 riccardo users 4096 set 24 10:07 .
drwx------ 83 riccardo users 12288 set 24 10:08 ..
-rw-r--r-- 1 riccardo users 57147506 set 24 10:05 0.00.89_FW_200_Askey
O valor 57147506 é o que precisamos.
Olhando para a saída do binwalk, podemos ver que a parte UBI começa em 3276894. Com esses dois valores, podemos usar dd assim:
$ dd if=0.00.89_FW_200_Askey of=0.00.89_FW_200_Askey.UBI bs=1 skip=3276894 count=57147506
Agora podemos usar ubidump (após alguma configuração inicial dependendo da configuração do sistema):
$ git clone https://github.com/nlitsme/ubidump.git
$ sudo pip install crcmod
$ yay -S python-lzo
$ python ubidump/ubidump.py -s bump 0.00.89_FW_200_Askey.UBI
Agora temos o sistema de arquivos em bump/rootfs_ubifs/
Agora podemos avançar um pouco mais na análise.
Olhando pelo sistema de arquivos, podemos ver que a interface web é gerenciada pelo mini_httpd:
$ grep -i -r "mini_httpd"
etc/sniproxy/urlfilter3.sh:ln -sf /usr/sbin/mini_httpd $URLFILTER_WEBD_PATH
etc/sniproxy/urlfilter3.sh:$URLFILTER_WEBD_PATH -d $URLFILTER_WEB_PATH -c "**.cgi" -u admin -S -E /web/mini_httpd.pem -p $webd_https_port
bin/web.sh:#mv /tmp/mini_httpd /usr/sbin
bin/web.sh:killall -9 mini_httpd
bin/web.sh:mini_httpd -d /web -c "STATUSAPI/**.cgi|STATUSAPI/*" -u admin -T "UTF-8" -p 8080
E podemos ver que o mini_httpd depende de um arquivo .cgi:
$ grep -i -r "status.cgi"
web/factory/mtcmd/mt_info.htm: "url":"/status.cgi",
web/fw-router/app/app.js:.value('apiServiceUrl', '/status.cgi')
**web/js/status.js:var CGI_NAME='status.cgi';**
web/js/status.js: "url":"http://192.168.101.93/STATUSAPI/status.cgi",
web/js/status.js: "url":"http://192.168.101.93/STATUSAPI/status.cgi",
web/js/status.js: "url":http://192.168.101.93/STATUSAPI/status.cgi,
Então, usando o Ghidra, podemos começar a examinar o status.cgi
Pulando muitos detalhes, a vulnerabilidade é descrita abaixo.
A primeira coisa a procurar é um método main como o desta imagem:

Podemos ver que setup é chamado. Depois de olhar um pouco, run_service(...) parece ser o núcleo de todas as funcionalidades.

Procurando por &service_tab, podemos ver que algumas strings específicas estão ligadas ao código real executado por run_service(...)

Depois de analisar esses métodos um por um, me deparei com usb_remove

Em detalhes:
mountVal = (char *)find_val(httpparams,"mount");
Pega a string da requisição url diretamente em uma variável
sprintf((char *)&mountLocation,"/mnt/%s",mountVal);
Cria um caminho completo a partir do parâmetro na requisição URL
mountLocation
mountLocation é usado diretamente em um comando
Agora, existem verificações para prevenir injeção de comando. Em particular:
invalidChars = 0x60273b7c;
É usado para verificar alguns caracteres inválidos como ` ' ; | mas & não está presente na lista, então se o parâmetro mount for igual a &ping -c 10 192.168.1.172&, a verificação permitirá a execução de /statusapi/bin/umount -lf &ping -c 10 192.168.1.172&.
O comando mount falhará, mas o ping seguinte será executado.
O patch publicado simplifica o código, eliminando as verificações de caracteres especiais ao simplesmente verificar se a pasta existe.

O código para explorar esta vulnerabilidade e habilitar o ssh está em fastwebEnableSSH.py https://github.com/garis/Fastgate/blob/master/fastwebEnableSSH.py