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:
- 37010035 - tamanho do pacote
- M2 é uma constante indicando o início de um pacote
- 0500ff01 - variável 0xff0005 no valor True
- 0600ff09 01 - variável 0xff0006 no valor 1 (Número de pacotes transmitidos)
- 0700ff09 07 - variável 0xff0007 no valor 7 (arquivo aberto no modo de leitura)
- 01000021 04 6967374 - lista de variáveis 0x01000001 de 4 bytes (geralmente essa variável é responsável pelo nome do arquivo)
- 0200ff88 02 ... 00 - uma matriz de 0xff0002 com um tamanho de 2 elementos
- 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 NvMessageTipos 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:
- marcadores especiais para indicar o início da embalagem
- tamanho do pacote
- 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:
- O caminho do arquivo não contém " .. ";
- Existem direitos para acessar o arquivo;
- 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
- O sistema operacional do roteador processa os pacotes recebidos sem problemas, mesmo antes da autorização do usuário
- 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.