Buen día, probablemente muchos han oído hablar de la vulnerabilidad reciente en los enrutadores Mikrotik, que le permite extraer las contraseñas de todos los usuarios. En este artículo, me gustaría mostrar y analizar en detalle la esencia de esta vulnerabilidad.
Todo el material se proporciona solo con fines informativos, por lo que el código que explota la vulnerabilidad no estará aquí. Si no está interesado en conocer las causas y la estructura interna de una vulnerabilidad en particular, puede seguir leyendo.
Empecemos
Lo primero que debe comenzar es el análisis de tráfico entre el cliente Winbox y el dispositivo.
Winbox es una aplicación para el sistema operativo Windows, que repite exactamente la interfaz web y está diseñada para administrar y configurar el dispositivo con el sistema operativo del enrutador a bordo. 2 modos de funcionamiento compatibles, TCP y UDP
Antes de comenzar, debe deshabilitar el cifrado de tráfico en Winbox. Esto se hace de la siguiente manera: es necesario incluir una garrapata
Herramientas ->
Modo avanzado. Después de eso, la interfaz cambiará de la siguiente manera:
Desmarque
Modo seguro . Inicie Wireshark e intente iniciar sesión en el dispositivo:
Como puede ver a continuación, después de la autorización, se solicita un archivo de
lista y luego se nos transfiere completamente su contenido, puede parecer que todo está bien, pero veamos el comienzo de esta sesión:
Al principio, Winbox envía exactamente el mismo paquete solicitando el archivo de
lista :
Considere su estructura:
- 37010035 - tamaño del paquete
- M2 es una constante que indica el inicio de un paquete
- 0500ff01 - variable 0xff0005 en el valor True
- 0600ff09 01 - variable 0xff0006 en valor 1 (Número de paquete transmitido)
- 0700ff09 07 - variable 0xff0007 en valor 7 (Abrir archivo en modo lectura)
- 01000021 04 6967374 - variable 0x01000001 lista de cadenas de 4 bytes (por lo general, esta variable es responsable del nombre del archivo)
- 0200ff88 02 ... 00 - una matriz de 0xff0002 con un tamaño de 2 elementos
- 0100ff88 02 ... 00 - una matriz de 0xff0001 con un tamaño de 2 elementos
Como resultado del protocolo inverso, y los archivos binarios correspondientes en el lado del cliente y del servidor, fue posible restaurar y comprender en mayor medida la estructura del protocolo por el cual Winbox se comunica con el dispositivo.
Descripción del protocolo NvMessageTipos de campo (nombre: designación digital)
- u32: 0x08000000
- u32_array: 0x88000000
- cadena: 0x20000000
- string_array: 0xA0000000
- addr6: 0x18000000
- addr6_array: 0x98000000
- u64: 0x10000000
- u64_array: 0x90000000
- verdadero: 0x00000000
- falso: 0x01000000
- bool_array: 0x80000000
- mensaje: 0x28000000
- matriz_mensajes: 0xA8000000
- sin procesar: 0x30000000
- raw_array: 0xB0000000
- u8: 0x09000000
- be32_array: 0x88000000
Tipos de errores (Nombre: designación 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 error (nombre: designación digital)
- ERROR_FAILED: 0xFE0006
- ERROR_TOOBIG: 0xFE000A
- ERROR_EXISTAS: 0xFE0007
- ERROR_NOTAL PERMITIDO: 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
- ESTADO_ERROR: 0x02
Estructura de campo en un paquete
Al comienzo de cualquier campo está su tipo - 4 bytes (3 bytes - el propósito de la variable, más adelante, 1 byte - directamente el tipo de esta variable), entonces la longitud es de 1-2 bytes y el valor en sí mismo.
Matrices
Figurativamente, la matriz se puede describir mediante la siguiente estructura:
struct Array { uint32 type; uint8 count; uint32 item1; uint32 item2; ... uint8 zero; }
Tipo (4 bytes) / Número de elementos (1 byte) / Elementos (4 bytes) / Al final del byte \ x00
Líneas
Las cadenas no tienen terminación nula, pero tienen una longitud claramente definida:
struct String { uint32 type; uint8 length; char text[length]; }
Los numeros
El tipo más simple en el paquete, se puede representar como un tipo de valor:
struct u* { uint32 type; uint8/32/64 value; }
Dependiendo del tipo, el valor tiene una dimensión de bit correspondiente.
Tipo booleano
El tamaño del campo es de 4 bytes, el byte alto es responsable del valor (True \ False), los 3 bytes inferiores son para asignar la variable
Además, cada paquete contiene:
- marcadores especiales para indicar el inicio del paquete
- tamaño del paquete
- grandes mercados de control de paquetes
Constantes encontradas
- 0xfe0001: contiene el identificador de sesión (1 byte)
- 0xff0006 - Número del paquete enviado (1 byte)
- 0xff0007 - Modo de acceso a archivos (1 byte)
Modos de acceso a archivos
- 7 - abierto para lectura
- 1 - abierto para grabación
- 6 - crear directorio
- 4 - leer
- 5 - eliminar
Ahora, sabiendo cómo funciona el protocolo, podemos generar aleatoriamente los paquetes que necesitamos y ver cómo el dispositivo responde a ellos.
En el lado del dispositivo, el ejecutable
/ nova / bin / mproxy es responsable del procesamiento de los paquetes. Como los nombres de las funciones no se guardaron, llamé a una función que procesa el paquete y toma decisiones sobre qué hacer con el archivo
file_handler () . Echa un vistazo a la función en sí:
PD El código que nos interesará está marcado con flechas.
Paso 1
Al recibir un paquete para abrir un archivo para leer, comienza a procesar desde este bloque:
Al principio, el nombre del archivo se extrae del paquete utilizando la función
nv :: message :: get <nv :: string_id> () .
Luego, la función
tokenize () divide la cadena resultante en partes separadas, utilizando el carácter "
/ " como delimitador.
La matriz resultante de cadenas se pasa a la función
path_filter () , que comprueba la matriz recibida de cadenas para la presencia de "
.. ", y en caso de errores devuelve un error
ERROR_NOTALLOWED (0xFE0009)PS ERROR_NOTALLOWED también se recibirá en la respuesta si no hay permisos de archivo
Si todo está bien, entonces la ruta al directorio
webfig o
pckg se concatena al comienzo del nombre del archivo
Paso 2
Si todo salió bien, el archivo se abre y su identificador se guarda en el objeto global.
Si no se pudo abrir el archivo, en la respuesta recibimos un error:
no se puede abrir el archivo fuente .
Por lo tanto, para recibir el contenido de un archivo, se deben cumplir 3 condiciones:
- La ruta del archivo no contiene " .. ";
- Hay derechos para acceder al archivo;
- El archivo existe y se puede abrir con éxito.
Ahora intentemos enviar algunos paquetes para probar la funcionalidad de esta función:
$ ./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
Entonces! Pero esto ya es extraño ... Recordamos que
ERROR_NOTALLOWED aparece si la verificación en
path_filter () no pasó, de lo contrario aún recibiríamos un mensaje sobre la falta de derechos de acceso, pero en el último caso, ¿resulta que el archivo se buscó en el directorio de nivel superior?
Probemos de esta manera:
$ ./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
Y funcionó. Pero por que? Echemos un vistazo al código de función
path_filter () :
El código muestra claramente que la búsqueda de la aparición de "
.. " en la matriz resultante de cadenas está realmente sucediendo. Pero luego, la parte más interesante, destaqué este fragmento en rojo.
La esencia de este código es que:
si el elemento anterior también es " .. ", entonces la verificación se considera fallida. De lo contrario, considere que todo está bien.
Es decir para que todo funcione, solo necesita alternar "
/./ " y "
/../ " para navegar con éxito a través de cualquier directorio y descender a cualquier nivel del FS.
Veamos cómo lo arreglaron los desarrolladores de Mikrotik:
Comparación de código pseudo-C Ahora la salida del ciclo de verificación ocurre en la primera detección de "
.. ". Es cierto que no está del todo claro para mí por qué agregaron una verificación para la ocurrencia de un punto. Y debido a un cambio en el mecanismo de activación del usuario
desarrollado , desafortunadamente, no hay forma de ver esto en la dinámica.
Para resumir
- El sistema operativo del enrutador procesa los paquetes entrantes sin problemas incluso antes de la autorización del usuario
- Debido a un filtro incorrecto, tenemos acceso a cualquier archivo
Dados los párrafos anteriores, podemos fácilmente: crear, eliminar, leer y escribir archivos, así como crear directorios arbitrarios
Por lo tanto, no es sorprendente que tener acceso para leer cualquier archivo sin autorización, lo primero que se hizo fue leer el archivo con las contraseñas de los usuarios. Afortunadamente, la red tiene mucha información sobre dónde se encuentra y cómo extraer datos de ella.
Además, esta vulnerabilidad puede ser un excelente reemplazo para la posibilidad previamente conocida de activar el modo desarrollador, ya que no necesita reiniciar el dispositivo, haga una
copia de seguridad /
restaure el archivo de configuración ahora.