Visão geral da vulnerabilidade do Mikrotik Winbox. Ou um arquivo grande

Bom dia, provavelmente muitos já ouviram falar da recente vulnerabilidade nos roteadores Mikrotik, que permite extrair as senhas de todos os usuários. Neste artigo, gostaria de mostrar e analisar em detalhes a essência dessa vulnerabilidade.
Todo o material é fornecido apenas para fins informativos; portanto, o código que explora a vulnerabilidade não estará aqui. Se você não estiver interessado em aprender sobre as causas e a estrutura interna de uma vulnerabilidade específica, continue lendo.

Vamos começar


A primeira coisa a começar é a análise de tráfego entre o cliente Winbox e o dispositivo
O Winbox é um aplicativo para o sistema operacional Windows, que repete exatamente a interface da web e foi projetado para administrar e configurar o dispositivo com o sistema operacional Router integrado. 2 modos de operação suportados, TCP e UDP
Antes de começar, você deve desativar a criptografia de tráfego no Winbox. Isso é feito da seguinte maneira: você precisa ativar a caixa de seleção Ferramentas -> Modo avançado . Depois disso, a interface mudará da seguinte maneira:


Desmarque o modo seguro . Inicie o Wireshark e tente fazer login no dispositivo:


Como você pode ver abaixo, após a autorização, um arquivo de lista é solicitado e, em seguida, seu conteúdo é completamente transferido para nós, pode parecer que está tudo bem, mas vamos ver o início desta sessão:


No início, o Winbox envia exatamente o mesmo pacote solicitando o arquivo de lista :


Considere sua estrutura:

  1. 37010035 - tamanho do pacote
  2. M2 é uma constante indicando o início de um pacote
  3. 0500ff01 - variável 0xff0005 no valor True
  4. 0600ff09 01 - variável 0xff0006 no valor 1 (Número de pacotes transmitidos)
  5. 0700ff09 07 - variável 0xff0007 no valor 7 (arquivo aberto no modo de leitura)
  6. 01000021 04 6967374 - lista de variáveis ​​0x01000001 de 4 bytes (geralmente essa variável é responsável pelo nome do arquivo)
  7. 0200ff88 02 ... 00 - uma matriz de 0xff0002 com um tamanho de 2 elementos
  8. 0100ff88 02 ... 00 - uma matriz de 0xff0001 com um tamanho de 2 elementos

Como resultado da reversão do protocolo e dos arquivos binários correspondentes no lado do cliente e do servidor, foi possível restaurar e entender em maior medida a estrutura do protocolo pelo qual o Winbox se comunica com o dispositivo.

Descrição do protocolo NvMessage

Tipos de campo (Nome: Designação Digital)


  • u32: 0x08000000
  • u32_array: 0x88000000
  • string: 0x20000000
  • matriz_de_string: 0xA0000000
  • addr6: 0x18000000
  • addr6_array: 0x98000000
  • u64: 0x10000000
  • u64_array: 0x90000000
  • true: 0x00000000
  • false: 0x01000000
  • bool_array: 0x80000000
  • mensagem: 0x28000000
  • message_array: 0xA8000000
  • bruto: 0x30000000
  • matriz_primária: 0xB0000000
  • u8: 0x09000000
  • be32_array: 0x88000000

Tipos de erros (Nome: Designação digital)


  • SYS_TO: 0xFF0001
  • STD_UNDOID: 0xFE0006
  • STD_DESCR: 0xFE0009
  • STD_FINISHED: 0xFE000B
  • STD_DYNAMIC: 0xFE0007
  • STD_INACTIVE: 0xFE0008
  • STD_GETALLID: 0xFE0003
  • STD_GETALLNO: 0xFE0004
  • STD_NEXTID: 0xFE0005
  • STD_ID: 0xFE0001
  • STD_OBJS: 0xFE0002
  • SYS_ERRNO: 0xFF0008
  • SYS_POLICY: 0xFF000B
  • SYS_CTRL_ARG: 0xFF000F
  • SYS_RADDR6: 0xFF0013
  • SYS_CTRL: 0xFF000D
  • SYS_ERRSTR: 0xFF0009
  • SYS_USER: 0xFF000A
  • SYS_STATUS: 0xFF0004
  • SYS_FROM: 0xFF0002
  • SYS_TYPE: 0xFF0003
  • SYS_REQID: 0xFF0006

Valores de erro (Nome: Designação Digital)


  • ERROR_FAILED: 0xFE0006
  • ERROR_TOOBIG: 0xFE000A
  • ERROR_EXISTS: 0xFE0007
  • ERROR_NOTALLOWED: 0xFE0009
  • ERROR_BUSY: 0xFE000C
  • ERROR_UNKNOWN: 0xFE0001
  • ERROR_BRKPATH: 0xFE0002
  • ERROR_UNKNOWNID: 0xFE0004
  • ERROR_UNKNOWNNEXTID: 0xFE000B
  • ERROR_TIMEOUT: 0xFE000D
  • ERROR_TOOMUCH: 0xFE000E
  • ERROR_NOTIMP: 0xFE0003
  • ERROR_MISSING: 0xFE0005
  • STATUS_OK: 0x01
  • STATUS_ERROR: 0x02

Estrutura de campo em um pacote


No início de qualquer campo, está o seu tipo - 4 bytes (3 bytes - o objetivo da variável, mais adiante, 1 byte - diretamente o tipo dessa variável); em seguida, o comprimento é de 1-2 bytes e o próprio valor.

Matrizes


Figurativamente, a matriz pode ser descrita pela seguinte estrutura:

struct Array { uint32 type; uint8 count; uint32 item1; uint32 item2; ... uint8 zero; } 

Tipo (4 bytes) / Número de elementos (1 byte) / Elementos (4 bytes) / No final do byte \ x00

Linhas


As strings não são terminadas em nulo, mas têm um comprimento claramente definido:

 struct String { uint32 type; uint8 length; char text[length]; } 

Os números


O tipo mais simples do pacote, pode ser representado como um tipo de valor:

 struct u* { uint32 type; uint8/32/64 value; } 

Dependendo do tipo, o valor tem uma dimensão de bit correspondente.

Tipo booleano


O tamanho do campo é de 4 bytes, o byte alto é responsável pelo valor (True \ False), os 3 bytes inferiores são para atribuir a variável

Além disso, cada pacote contém:

  1. marcadores especiais para indicar o início da embalagem
  2. tamanho do pacote
  3. grandes mercados de controle de pacotes


Constantes encontradas


  • 0xfe0001 - Contém o identificador da sessão (1 byte)
  • 0xff0006 - Número do pacote enviado (1 byte)
  • 0xff0007 - Modo de acesso a arquivos (1 byte)

Modos de acesso a arquivos

  • 7 - aberto para leitura
  • 1 - aberto para gravação
  • 6 - criar diretório
  • 4 - ler
  • 5 - excluir


Agora, sabendo como o protocolo funciona, podemos gerar aleatoriamente os pacotes de que precisamos e observar como o dispositivo responde a eles.

No lado do dispositivo, o executável / nova / bin / mproxy é responsável pelo processamento dos pacotes. Como os nomes das funções não foram salvas, chamei uma função que processa o pacote e toma decisões sobre o que fazer com o arquivo file_handler () . Dê uma olhada na própria função:


PS O código que nos interessará está marcado com setas.

Etapa 1


Ao receber um pacote para abrir um arquivo para leitura, ele inicia o processamento a partir deste bloco:


No início, o nome do arquivo é extraído do pacote usando a função nv :: message :: get <nv :: string_id> () .

Em seguida, a função tokenize () divide a sequência resultante em partes separadas, usando o caractere " / " como delimitador.

A matriz resultante de strings é passada para a função path_filter () , que verifica a matriz recebida de strings quanto à presença de " .. " e, no caso de erros, retorna um erro ERROR_NOTALLOWED (0xFE0009)


PS ERROR_NOTALLOWED também será recebido na resposta se não houver permissões de arquivo

Se tudo estiver bem, o caminho para o diretório webfig ou pckg será concatenado no início do nome do arquivo

Etapa 2



Se tudo der certo, o arquivo é aberto e seu identificador é salvo no objeto global.

Se o arquivo não pôde ser aberto, na resposta, recebemos um erro: não é possível abrir o arquivo de origem .


Assim, para receber o conteúdo de um arquivo, três condições devem ser atendidas:

  1. O caminho do arquivo não contém " .. ";
  2. Existem direitos para acessar o arquivo;
  3. O arquivo existe e pode ser aberto com sucesso.

Agora vamos tentar enviar alguns pacotes para testar a funcionalidade desta função:

 $ ./untitled.py -t 192.168.88.1 -f /etc/passwd Error: SYS_ERRNO => ERROR_FAILED Error: SYS_ERRSTR => cannot open source file $ ./untitled.py -t 192.168.88.1 -f /../../../etc/passwd Error: SYS_ERRNO => ERROR_NOTALLOWED $ ./untitled.py -t 192.168.88.1 -f //./././././../etc/passwd Error: SYS_ERRNO => ERROR_FAILED Error: SYS_ERRSTR => cannot open source file 

Então! Mas isso já é estranho ... Lembramos que ERROR_NOTALLOWED aparece se o check-in path_filter () não for aprovado; caso contrário, ainda receberíamos uma mensagem sobre a falta de direitos de acesso, mas no último caso, verifica-se que o arquivo foi pesquisado no diretório de nível superior?

Vamos tentar desta maneira:

 $ ./untitled.py -t 192.168.88.1 -f //./.././.././../etc/passwd xvM2        1Enobody:*:99:99:nobody:/tmp:/bin/sh root::0:0:root:/home/root:/bin/sh 

E funcionou. Mas porque? Vamos dar uma olhada no código da função path_filter () :


O código mostra claramente que a pesquisa pela ocorrência de " .. " na matriz resultante de seqüências de caracteres está realmente acontecendo. Mas então a parte mais interessante, destaquei esse fragmento em vermelho.
A essência desse código é que: Se o item anterior também for " .. ", a verificação será considerada falhada. Caso contrário, considere que está tudo bem.

I.e. para que tudo funcione, basta alternar " /./ " e " /../ " para navegar com êxito por qualquer diretório e descer a qualquer nível do FS.


Vamos ver como os desenvolvedores do Mikrotik o consertaram:


Comparação de código pseudo-C


Agora a saída do ciclo de verificação ocorre na primeira detecção de " .. ". É verdade que não está totalmente claro para mim por que eles adicionaram uma verificação para a ocorrência de um ponto. E devido a uma alteração no mecanismo de ativação do usuário devel , infelizmente, não há como ver isso na dinâmica.

Resumir


  1. O sistema operacional do roteador processa os pacotes recebidos sem problemas, mesmo antes da autorização do usuário
  2. Devido a um filtro incorreto, temos acesso a qualquer arquivo

Dados os parágrafos anteriores, podemos facilmente: criar, excluir, ler e gravar arquivos, além de criar diretórios arbitrários

Portanto, não é de surpreender que, tendo acesso para ler arquivos sem autorização, a primeira coisa que foi feita foi a leitura do arquivo com senhas de usuário. Felizmente, a rede possui muitas informações sobre onde está localizada e como extrair dados dela.

Além disso, essa vulnerabilidade pode ser um excelente substituto para a possibilidade conhecida anteriormente de ativar o modo de desenvolvedor, porque você não precisa reiniciar o dispositivo, faça backup \ restaure o arquivo de configuração agora.

Source: https://habr.com/ru/post/pt416067/


All Articles