Exploração de um dispositivo VPN: a jornada de um pesquisador
- O pesquisador da Akamai Ben Barnea descobriu diversas vulnerabilidades no FortiOS da Fortinet.
- Um invasor sem autenticação pode explorar essas vulnerabilidades, resultando em ataques DoS e RCE
- A vulnerabilidade de DoS é de fácil exploração e pode tornar o dispositivo Fortigate inoperante.
- Acreditamos que a exploração da vulnerabilidade de RCE seja mais complexa.
- As vulnerabilidades foram reportadas de forma responsável à Fortinet e receberam as identificações CVE-2024-46666 e CVE-2024-46668.
- A Fortinet corrigiu as vulnerabilidades descobertas por Barnea em 14 de janeiro de 2025, e dispositivos atualizados com a versão mais recente do FortiOS estão protegidos.
Introdução
Nos últimos anos, soluções VPN têm apresentado diversas vulnerabilidades críticas que foram exploradas ativamente por agentes mal-intencionados. Algumas dessas vulnerabilidades são incrivelmente fáceis de explorar e causam um impacto devastador: uma RCE em um dispositivo VPN exposto à Internet. Uma vez dentro da rede, os invasores podem se movimentar lateralmente para acessar dados confidenciais, propriedade intelectual e outros ativos valiosos.
Além da exploração inicial das vulnerabilidades em VPNs, o pesquisador da Akamai, Ori David, também documentou técnicas pós-exploração e demonstrou como um servidor VPN comprometido pode permitir que os invasores assumam facilmente o controle sobre outros ativos críticos na rede.
Infelizmente, pesquisadores de segurança que desejam analisar soluções VPN enfrentam dificuldades para iniciar suas pesquisas, pois os firmwares nem sempre estão facilmente disponíveis e são protegidos por mecanismos de criptografia implementados pelos fornecedores. No entanto, como dispositivos VPN são alvos prioritários para exploração, superar essas proteções pode ser altamente vantajoso para invasores.
Neste artigo, exploramos o processo de pesquisa da solução VPN da Fortinet. Vamos abordar as etapas de obtenção do firmware, descriptografia, configuração de um depurador e, por fim, a busca por vulnerabilidades.
Algumas das técnicas apresentadas aqui não são inéditas — já houve pesquisas relevantes sobre o FortiOS conduzidas por Optistream, Bishop Fox, Assetnote, Lexfo e muito mais. Nós atualizamos essas descobertas com a versão mais recente do FortiOS, pois a Fortinet frequentemente altera os métodos de criptografia e descriptografia, dificultando a análise do dispositivo.
Obtenção de uma imagem de firmware
Tradicionalmente, as VPNs eram vendidas como dispositivos físicos dedicados, o que tornava sua aquisição e extração de firmware mais complexas. Hoje em dia, no entanto, é comum encontrar soluções VPN em formato de dispositivo virtual, implantáveis em máquinas virtuais (VM).
Felizmente, a Fortinet disponibiliza uma versão de avaliação da sua VM, que pode ser baixada após um registro no website (Figura 1). Essa VM possui algumas limitações: permite apenas um processador e tem um limite de 2 GB de RAM.
Criação de um ambiente de depuração
A VM fornecida contém dois pontos de interesse: (1) uma imagem de inicialização e uma imagem de kernel (chamada flatkc), e (2) um rootfs de sistema de arquivos criptografado que contém a maioria dos arquivos mais relevantes. Dentro desse sistema de arquivos, após a descriptografia, encontramos um binário chamado init no diretório /bin/.
A maioria dos binários da VM foi compilada estaticamente nesse binário. Dois binários particularmente interessantes dentro de /bin/init são o SSLVPND e o servidor web de gerenciamento. Voltaremos aos binários mais adiante.
Nosso objetivo é criar um ambiente com acesso total ao shell, em vez da interface de linha de comando (CLI) limitada fornecida pela Fortinet. Além disso, queremos adicionar o GDB, para facilitar a depuração dos binários.
Para isso, seguimos os passos abaixo:
- Descompactar o arquivo GZIP do CPIO (um formato de arquivamento de arquivos)
- Utilizar o script do Bishop Fox para descriptografar o rootfs
- Descompactar o arquivo bin.tar.xz
- Aplicar um patch de verificação de integridade do /bin/init
- Converter flatkc para o formato ELF utilizando vmlinux-to-elf
- Localizar o endereço da função fgt_verify_initrd no IDA e aplicar um patch em tempo de execução para desativar verificações adicionais de integridade
- Adicionar versões estáticas do BusyBox e do GDB dentro de /bin/
- Compilar um stub que cria um servidor Telnet e substituir /bin/smartctl por esse stub
- Compactar a pasta /bin/
- Reempacotar e criptografar o rootfs
- Adicionar um preenchimento no final do rootfs criptografado
- Substituir o rootfs dentro do VMDK utilizando uma VM auxiliar com Ubuntu (que monta o VMDK)
As etapas ilustradas na Figura 2 criam uma VM editada com suporte à depuração. Como a verificação de integridade do rootfs falha, o kernel interrompe a execução. Portanto, precisamos ou corrigir o kernel (flatkc) e também aplicar patch ao código de verificação de integridade do bootloader, ou recorrer a um patch dinâmico para ignorar essa verificação. Optamos por seguir a segunda abordagem.
Tentamos usar o recurso de depuração de VM da VMware editando o arquivo VMDK. Isso deve configurar um depurador GDB que estará disponível quando a máquina for executada. Infelizmente, encontramos problemas ao implementar esse recurso em uma máquina com o Hyper-V ativado. Assim que um ponto de interrupção era atingido, a máquina travava. Isso parece ocorrer devido a uma implementação incompleta do recurso de depuração do kernel quando executado em uma máquina com Hyper-V.
Criação de uma VM funcional com QEMU
Após várias tentativas frustradas de depuração do kernel na VMware, decidimos tentar criar uma VM funcional utilizando QEMU. Geramos a VM modificada de forma estática seguindo um processo semelhante, com a diferença de precisar converter os arquivos entre os formatos qcow2 e VMDK.
Para depurar o kernel ao usar o QEMU, podemos fornecer o sinalizador -s para qemu-system. Por fim, executamos a VM, conectamos o GDB, adicionamos um ponto de interrupção para substituir a verificação de integridade e, assim, conseguimos acessar a CLI. (Figura 3).
Após configurar a rede e receber um IP válido do servidor DHCP, conseguimos executar o binário modificado do smartctl, que exibe o conteúdo do diretório atual, o comando do ID do Linux e abre uma sessão telnet via BusyBox. (Figura 4).
Por fim, a Figura 5 mostra que conseguimos nos conectar ao novo servidor telnet.
O trabalho já está concluído? Não.
Como mostrado na Figura 6, não temos uma licença válida, o que nos impede de interagir com o painel administrativo.

Você não deve (?) contornar
A princípio, acreditamos que a licença fosse inválida porque ultrapassamos a limitação de 1 CPU e 2.048 MB de RAM. Diante disso, tínhamos duas opções: aceitar essa limitação e ter uma VM extremamente lenta ou contorná-la.
Após algumas reversões, encontramos a função upd_vm_check_license que é chamada periodicamente em um daemon (Figura 7). A função verifica se a quantidade de RAM e o número de CPUs da máquina não ultrapassam os limites estabelecidos.
Ao contornar as restrições e modificar dinamicamente o valor de retorno das funções num_max_CPUs() e max_allowed_RAM(), conseguimos uma VM poderosa sem restrições e reduzimos a ocorrência de erros durante a inicialização, mas ainda enfrentávamos o erro de licença inválida.
Após gastar muito tempo revertendo a função de validação da licença (e repensando nossas escolhas de vida que nos levaram a isso), descobrimos finalmente que a licença era gerada com base na chave de série da máquina, que, por sua vez, é construída a partir do UUID do SMBIOS. Como não fornecemos um UUID ao QEMU, o sistema usou um NULL e gerou um número de série padrão: "FGVMEV0000000000". Ao definir um UUID do SMBIOS no QEMU com o seguinte sinalizador:
-smbios type=1,manufacturer=t1manufacturer,product=t1product,version=t1version,serial=t1serial,
uuid=25359cc8-5fe7-4d50-ab82-9fd15ecaf221,sku=t1sku,family=t1family
Conseguimos finalmente inicializar a máquina com uma licença válida.
Nova versão. Nova criptografia. POR QUÊ?
Neste momento, já temos um ambiente funcional para depuração. Iniciamos a análise do servidor web de gerenciamento e identificamos algumas vulnerabilidades, que serão detalhadas mais adiante. Durante essa pesquisa, notamos que a Fortinet lançou o FortiOS 7.4.4.
Nosso objetivo era verificar se as vulnerabilidades ainda existiam na nova versão. No entanto, ao tentar descriptografar o rootfs da VM atualizada, percebemos que o método de criptografia havia sido completamente alterado e estava ainda mais difícil de quebrar. Dessa vez, decidimos fazer um esforço para descriptografar o novo rootfs, mas não para criar um ambiente de depuração, pois o principal objetivo neste ponto era verificar se as vulnerabilidades ainda existiam.
Então, primeiro, vamos descrever a maneira antiga (anterior à v. 7.4.4) de descriptografar o rootfs:
1. O kernel verifica a integridade do rootfs e, se for válido, continua para a próxima etapa.
2. O kernel chama fgt_verifier_key_iv, que calcula uma chave e o IV da seguinte forma:
a. Chave: sha256() de dados globais
b. IV: sha256() de outra parte dos mesmos dados globais; em seguida, trunca o resultado para 16 bytes
3. Descriptografa o rootfs usando Chacha20 com a chave e o IV acima.
Agora, vamos dar uma olhada no novo algoritmo (Figura 8):
1. O código de descriptografia calcula a chave e o IV por sha256() de um buffer de dados global, como no algoritmo anterior.
2. O ChaCha descriptografa um bloco de memória usando a chave e o IV; esse bloco de memória é um ASN1 que representa uma chave privada RSA.
3. Extrai d e n da chave privada RSA e descriptografa um bloco de dados presente nos últimos 256 bytes do firmware criptografado usando a fórmula conhecida: M = Cd mod N.
4. A partir do bloco de dados, leva:
16 bytes que são nonce e counter
32 bytes principais
5. Executa a descriptografia AES no modo CTR, utilizando a chave e nonce + counter; o código emprega uma adição CTR personalizada.
6. A adição é o resultado da operação XOR entre os nibbles de (nonce + counter)
Para aumentar a complexidade desse algoritmo de descriptografia em múltiplas etapas, o novo flatkc não possui mais símbolos, o que dificulta a criação de ferramentas para descriptografar o firmware automaticamente — por exemplo, localizar os dados globais usados na descriptografia ChaCha e a chave privada RSA criptografada.
Após realizar todas as etapas descritas acima, podemos visualizar o rootfs (Figura 9).
Dessa vez, não criamos um ambiente modificado. Isso exige a criação de um arquivo rootfs que precisa ser descriptografado com sucesso, conforme descrito acima. Outra opção é definir pontos de interrupção dinamicamente e substituir a memória com um rootfs descriptografado.
Reversão do servidor web de administração
Por fim, podemos abordar a reversão do servidor web de administração. Ele é baseado no Apache e, em geral, não deve ser acessível à internet (ao contrário da interface sslvpn, que é acessível à internet).
Ao abrir a configuração httpd, podemos notar algumas diretivas de localização que direcionam URLs para seus manipuladores (Figura 10).
Em seguida, podemos procurar uma das strings do manipulador no binário para encontrar a tabela de manipuladores (Figura 11).
Como estávamos interessados em encontrar vulnerabilidades não autenticadas, decidimos focar no api_authentication-handler, que é acessível através do URL /api/v2/authentication.
Antes de começar o trabalho de reversão, é recomendável criar algumas estruturas Apache e uma estrutura de conexão no IDA para facilitar o trabalho (Figuras 12 e 13).
struct __attribute__((aligned(8))) _request_rec
{
apr_pool_t *pool;
conn_rec *connection;
void *server;
_request_rec *next;
_request_rec *prev;
_request_rec *main;
char *the_request;
int assbackwards;
int proxyreq;
int header_only;
int proto_num;
char *protocol;
const char *hostname;
unsigned __int64 request_time;
const char *status_line;
int status;
enum http_methods method_number;
const char *method;
unsigned __int64 allowed;
void *allowed_xmethods;
void *allowed_methods;
unsigned __int64 sent_bodyct;
unsigned __int64 bytes_sent;
unsigned __int64 mtime;
const char *range;
unsigned __int64 clength;
int chunked;
int read_body;
int read_chunked;
unsigned int expecting_100;
void *kept_body;
void *body_table;
unsigned __int64 remaining;
unsigned __int64 read_length;
void *headers_in;
void *headers_out;
void *err_headers_out;
void *subprocess_env;
void *notes;
const char *content_type;
const char *handler;
const char *content_encoding;
void *content_languages;
char *vlist_validator;
char *user;
char *ap_auth_type;
char *unparsed_uri;
char *uri;
char *filename;
char *canonical_filename;
char *path_info;
char *args;
int used_path_info;
int eos_sent;
void *per_dir_config;
void *request_config;
void *log;
const char *log_id;
void *htaccess;
void *output_filters;
void *input_filters;
void *proto_output_filters;
void *proto_input_filters;
int no_cache;
int no_local_copy;
void *invoke_mtx;
apr_uri_t parsed_uri;
apr_finfo_t finfo;
void *useragent_addr;
char *useragent_ip;
void *trailers_in;
void *trailers_out;
char *useragent_host;
int double_reverse;
unsigned __int64 bnotes;
};
Fig. 12: Estruturas Apache
struct __attribute__((aligned(8))) conn_rec
{
apr_pool_t *pool;
void *base_server;
void *vhost_lookup_data;
apr_sockaddr_t *local_addr;
sockaddr *client_addr;
char *client_ip;
char *remote_host;
char *remote_logname;
char *local_ip;
char *local_host;
__int64 id;
void *conn_config;
void *notes;
void *input_filters;
void *output_filters;
void *sbh;
void *bucket_alloc;
void *cs;
int data_in_input_filters;
int data_in_output_filters;
unsigned __int32 clogging_input_filters : 1;
__int32 double_reverse : 2;
unsigned int aborted;
ap_conn_keepalive_e keepalive;
int keepalives;
void *log;
const char *log_id;
conn_rec *master;
int outgoing;
};
Fig. 13: Uma estrutura de conexão
Ao reverter o manipulador de autenticação, primeiro revertemos o manipulador do método POST, chamado api_login_handler. A função recupera os parâmetros de login da solicitação chamando api_login_parse_param. Essa função tenta analisar os dados do POST dependendo do cabeçalho content-type:
Se definido como "multipart/form-data", a solicitação contém um formulário HTML.
Caso contrário, lê os dados POST simples.
A segunda opção é bastante simples, então focamos principalmente na primeira parte. Ao dar uma olhada rápida no código decompilado, notamos rapidamente uma string de depuração que nos direcionou para a biblioteca libapreq (Figura 14).
Como libapreq é uma biblioteca Apache de código aberto, (quase) não havia razão para procurar vulnerabilidades no código decompilado em vez do código-fonte. Portanto, a primeira coisa que precisávamos fazer era encontrar a versão da biblioteca. Após várias idas e vindas, conseguimos restringir a versão ao encontrar uma string presente no binário e em um commit específico, mas que foi removida em um commit posterior (Figura 15).
A parte surpreendente é que a biblioteca presente no binário é a versão mais antiga disponível, de março de 2000 (Figura 16).
Vulnerabilidades
A Fortinet utiliza o módulo quase exatamente como estava há 25 anos, com exceção de pequenas mudanças para otimização. Quando vimos isso pela primeira vez, achamos que não havia chance de o código de 2000 não ter vulnerabilidades. E estávamos certos!
Antes de analisarmos as vulnerabilidades, vamos explicar o propósito e o uso da biblioteca. Apreq é uma biblioteca Apache usada para manipular dados de solicitação de cliente. Uma maneira comum de receber dados de um usuário são os formulários HTML. Os dados preenchidos no formulário podem ser enviados para o servidor usando diferentes métodos de codificação, mas os métodos comuns são application/x-www-form-urlencoded e multipart/form-data.
Quando multipart/form-data é usado, o cliente (geralmente o navegador) escolhe um texto arbitrário como delimitador entre os diferentes campos dos dados do formulário. O delimitador é especificado através de um cabeçalho HTTP. Ele também é usado para indicar o final dos dados do formulário (Figura 17).
POST /foo HTTP/1.1
Content-Length: 68137
Content-Type: multipart/form-data; boundary=ExampleBoundaryString
--ExampleBoundaryString
Content-Disposition: form-data; name="description"
Description input value
--ExampleBoundaryString
Content-Disposition: form-data; name="myFile"; filename="foo.txt"
Content-Type: text/plain
[content of the file foo.txt chosen by the user]
--ExampleBoundaryString--
Figura 17: Exemplo de um formulário de delimitador HTTP (Fonte)
Agora, vamos dar uma olhada em algumas das vulnerabilidades que encontramos, incluindo gravação fora dos limites (OOB) de byte NULL, cópia errada, DoS de dispositivo, DoS de servidor web e leitura fora dos limites (OOB).
Gravação OOB de byte NULL
Após o multipart_buffer_read preencher o buffer interno e procurar pelo delimitador, ele retorna a string entre a posição atual e o delimitador encontrado. O erro é que, se o delimitador não estiver no início do buffer interno, ele retornará a string após remover os dois últimos caracteres que deveriam ser a quebra de linha ("\r\n"). O código supõe incorretamente que o comprimento da string retornada é maior que 2.
Na Figura 18, retval é a string retornada e start é o seu comprimento, que é igual a 1. Nesse caso, blen também é igual a start. Ele é então decrementado em dois para o valor -1. Assim, temos a capacidade de gravar NULL um byte antes do buffer.
Tentativas de exploração
Embora um estouro de um byte — ou até mesmo um estouro de um bit — possa ser suficiente para alcançar a execução de código, acreditamos que essa vulnerabilidade é improvável de ser explorada na prática. Primeiro, só podemos gravar um byte NULL, e apenas um byte antes do buffer. O buffer é alocado na heap; portanto, existem duas opções:
1. O buffer é o primeiro a ser alocado no nó da heap. Nesse caso, teremos os metadados do nó da heap antes da nossa alocação (Figura 19).
Vamos substituir um byte do ponteiro endp. Isso não afetaria o valor do ponteiro, pois substituiríamos o byte mais alto do ponteiro devido à ordem de bytes (endianness). Como a VM é x64, esse byte sempre será 0.
2. Se já houver uma alocação anterior à nossa, poderemos substituir um byte de dados. Infelizmente, como no exemplo anterior, na maioria dos casos, teremos um ponteiro no final da estrutura, preenchimento (padding) ou uma string C já terminada em NULL.
Encontramos um objeto interessante: a estrutura multipart_buffer em C (Figura 20).
Nesse caso, pensamos que poderíamos fazer com que buffer_len, a última variável da estrutura, fosse negativa por meio da vulnerabilidade anterior, e então mudá-la de negativo para um valor positivo grande com essa vulnerabilidade (substituindo o byte MSB que marca o número como negativo).
Embora isso parecesse uma abordagem interessante, haviam dois problemas:
1. A estrutura é criada apenas uma vez — ao criar o analisador de formulários. Isso significa que não conseguiríamos pulverizar facilmente esse objeto.
2. Uma vez que usamos a vulnerabilidade anterior, limitamos o comprimento lido na função de preenchimento. Isso significa que, uma vez que a função de preenchimento termine, self->length será 0 após ler todos os dados POST da requisição. Na próxima vez que o código chamar multipart_buffer_read, ele não encontraria o delimitador no buffer avançado (já que fizemos com que o ponteiro final aparecesse antes de seu início) e, como self->length é 0, ele sairia do alerta de um carregamento malformado.
Pensamos em tentar explorar isso em uma tentativa de condição de corrida, mas ao olhar para o modo de multiprocessamento do Apache (MPM), vemos a Figura 21.
Isso significa que o Apache criará vários processos, cada um lidando com uma requisição. Também podemos notar que esses processos não são multithreaded. Isso significa que não seremos capazes de explorá-lo em condição de corrida.
Cópia errada
Na mesma função, multipart_buffer_read, se o código não encontrar o delimitador (start igual a -1), ele retornará apenas parte do buffer interno: (bytes - boundary_length). O erro aqui é que bytes é definido como o valor constante 5120, enquanto que o comprimento do delimitador pode ser muito maior (até o limite do comprimento do cabeçalho).
Assim, ao enviar um campo em que o delimitador não está no primeiro pedaço e o comprimento do delimitador é maior que 5120, poderemos fazer com que blen seja negativo. Isso leva o código a definir self->buffer para antes do buffer e self->buffer_len para um valor maior (Figura 22).
Tentativas de exploração
Há uma diferença entre essa vulnerabilidade e a anterior — desta vez, já que start é negativo (o delimitador não foi encontrado), não chegamos no código que grava um byte NULL.
O blen é um parâmetro para a função multipart_buffer_read, então vamos observar a função multipart_buffer_read_body que a chama e recebe blen como saída.
A Figura 23 mostra que blen é usado duas vezes:
1. Se for o primeiro buffer criado, então a string recebida de multipart_buffer_read será duplicada usando blen. Nesse caso, o Apache gera um erro de falta de memória (OOM) e o código é abortado. Isso pode ser usado para lançar um ataque de negação de serviço (DoS).
2. Se for o segundo pedaço, o anterior e o atual serão combinados usando a função my_join. A função chama memcpy com um número negativo, levando a uma cópia errada.
(Alguns de vocês podem ter notado outro bug nesse código; o old_len é atualizado para ser blen ao invés de old_len + blen, levando a uma truncagem dos dados do usuário.)
Explorar essa cópia errada seria muito difícil, se é que seria possível. Primeiro, não temos nenhuma opção para “parar” a cópia errada: é um memcpy com um tamanho grande. Segundo, não há multithreading, então não podemos substituir um objeto que será usado em outro thread simultaneamente. Supomos que a única opção possível seria explorar o manipulador de sinais se ele não realizar uma saída segura.
DoS do dispositivo
Essa vulnerabilidade na verdade não está na biblioteca em si, mas no código da Fortinet que usa a biblioteca.
Quando o usuário carrega um arquivo através do formulário, especificado com um cabeçalho Content-Disposition dentro dos dados POST (veja a Figura 17), um novo arquivo é criado na pasta /tmp/ com o nome de arquivo /tmp/uploadXXXXXX, onde os Xs são caracteres aleatórios.
Para cada upload de arquivo, uma estrutura apropriada é criada e inserida em uma lista vinculada de arquivos carregados. O erro aparece no final da análise, quando apenas o arquivo no primeiro nó da lista vinculada é excluído. Isso permite que um invasor inicie um ataque preenchendo a pasta /tmp/. Como /tmp/ é um sistema de arquivos tmpfs, os dados são armazenados na RAM. Isso leva a um caso de OOM (sem memória) completo do sistema, o que faz com que o dispositivo fique travado.
Somente a reinicialização do dispositivo o retorna ao uso normal, e nem isso é garantido. Em uma de nossas tentativas, causamos algum tipo de network brick no dispositivo: Mesmo após reiniciar o dispositivo, a funcionalidade de rede não funcionava corretamente, e não conseguimos usar ou conectar ao dispositivo.
Tentativas de exploração
Essa vulnerabilidade é bem fácil de explorar. Basta enviar repetidamente requisições com um formulário contendo vários arquivos. Após um curto período, o dispositivo ficará travado (Figura 24).
DoS do servidor web
Esta é uma vulnerabilidade menor na função multipart_buffer_headers. Ela chama multipart_buffer_fill, que preenche o buffer interno, e depois procura por um duplo fim de linha no buffer interno.
O bug é que o código não verifica se o buffer interno é válido após chamar multipart_buffer_fill. Se o cliente fechar a conexão enquanto multipart_buffer_fill estiver esperando por entrada, ele definirá o buffer como NULL, o que leva a uma desreferência de NULL (Figura 25).
Tentativas de exploração
Essa vulnerabilidade também é bastante fácil de explorar. Um invasor pode criar múltiplos threads que criam uma conexão e enviam a requisição de falha. Como o MPM pré-fork do Apache não oferece um bom desempenho, o servidor não é capaz de lidar com as várias falhas e não consegue atender a outros clientes enquanto o ataque estiver em andamento.
Leitura fora dos limites
A biblioteca usa um buffer interno que é preenchido regularmente. Quando ele é preenchido, o cálculo de quantos bytes ler é o seguinte:
1. Calcular (bytes_requested - current_internal_buffer_len).
2. Adicionar o comprimento do delimitador + 2 (para "\r\n\r\n").
3. Calcular o mínimo entre esse resultado e os bytes disponíveis para ler dos dados POST.
A vulnerabilidade está em multipart_buffer_read. Se ele encontrou o delimitador no início do buffer interno, e não for o delimitador que marca o fim do formulário, ele avançará o ponteiro do buffer interno além do delimitador e adicionará mais 2 bytes para o fim da linha.
O erro aqui é que um invasor pode fazer o buffer interno ser exatamente do tamanho do delimitador, fazendo com que o código avance além do fim do buffer interno em dois bytes (Figura 26). Isso pode ser feito, pois podemos "limitar" o cálculo do buffer interno fornecendo uma quantidade menor de bytes do que boundary_length + bytes_requested.
Tentativas de exploração
Como visto na Figura 26, o código retorna NULL. No entanto, como vimos no bug anterior, o código em multipart_buffer_headers acessa o buffer interno diretamente, e não o valor retornado. Isso faz com que o código procure o "\r\n" após o buffer e o retorne como um cabeçalho.
No uso do apreq no manipulador de login, o código lê os campos do formulário, mas não os envia de volta para o cliente, então não podemos usar a vulnerabilidade OOB como uma vazamento de informações nesse caso. A biblioteca também é usada várias vezes no servidor web, mas parece que essas são disponíveis apenas para usuários autenticados, então, no máximo, um invasor com credenciais de usuário de baixo privilégio poderia usá-la como uma vulnerabilidade de escalonamento de privilégios, lendo credenciais de usuário de alto privilégio da memória.
SSLVPND
SSLVPND é o daemon responsável por gerenciar o componente SSL-VPN da Fortinet. É acessível à Internet. A biblioteca apreq também é usada no SSLVPND. A biblioteca usada lá é baseada na mesma versão antiga de apreq, com algumas modificações. Todas as vulnerabilidades descritas acima, com exceção da vulnerabilidade DoS do dispositivo, também existem no SSLVPND.
Infelizmente, não conseguimos acionar a biblioteca apreq no SSLVPND, então não podemos confirmar se essas vulnerabilidades estão acessíveis para usuários não autenticados ou se elas poderiam ser exploradas no contexto do SSLVPND.
Resumo
Os invasores frequentemente visam VPNs devido à sua natureza de serem acessíveis pela internet. Neste post, analisamos um exemplo de abordagem para pesquisar em um dispositivo VPN. Embora não tenham sido identificadas vulnerabilidades críticas, acreditamos que é seguro supor que há vulnerabilidades adicionais esperando para serem descobertas.
Como as VPNs são um ponto de entrada para a rede da organização, falhas nesses sistemas podem ter um grande impacto nas empresas. Esperamos que este post também incentive outros pesquisadores de segurança a procurar vulnerabilidades em VPNs.