
Este repositorio tiene una publicación de blog sobre mi análisis de CVE-2018-19987, una inyección de comandos del sistema operativo autenticada que afecta a múltiples routers D-Link.
¡Hola a todos!
En esta breve entrada de blog desglosaré un análisis rápido que realicé para comprender mejor los detalles detrás de CVE-2018-19987. Al principio fui tras este CVE porque pensé que no había información pública ni exploit. Poco después de hacer un primer análisis, encontré dos PoCs públicos en GitHub que puedes encontrar aquí y aquí.
Como ya había hecho la mayor parte del trabajo y no había encontrado un análisis completo, decidí escribir la entrada de todos modos.
¡Espero que lo disfrutes!
Actualización: Bueno... después de buscar en Google mientras estaba atascado con esta entrada de blog, encontré el siguiente artículo que describe una vulnerabilidad muy similar (CVE-2018-19986) en el D-Link DIR-818. Serie de análisis de vulnerabilidades de routers (5): análisis y reproducción de la vulnerabilidad de inyección de comandos CVE-2018-19986 DIR-818LW&828 (traducido por Google) por Ogur1.
Comencé mi análisis recopilando toda la información que pude sobre el dispositivo:
Nota: Realicé todo el análisis con el firmware v3.01B02.
La información pública de esta vulnerabilidad, disponible en la página de Mitre, proporcionó suficiente información para empezar:
D-Link DIR-822 Rev.B 202KRb06, DIR-822 Rev.C 3.10B06, DIR-860L Rev.B 2.03.B03, DIR-868L Rev.B 2.05B02, DIR-880L Rev.A 1.20B01_01_i3se_BETA, and DIR-890L Rev.A 1.21B02_BETA devices mishandle IsAccessPoint in /HNAP1/SetAccessPointMode. In the SetAccessPointMode.php source code, the IsAccessPoint parameter is saved in the ShellPath script file without any regex checking. After the script file is executed, the command injection occurs. A vulnerable /HNAP1/SetAccessPointMode XML message could have shell metacharacters in the IsAccessPoint element such as the `telnetd` string.
Para comenzar el análisis necesitaba algo que examinar, así que procedí a extraer el FS del router.
binwalk -eM DIR822C1_FW301WWb02.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 DLOB firmware header, boot partition: "dev=/dev/mtdblock/1"
10380 0x288C LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 4213444 bytes
1376372 0x150074 PackImg section delimiter tag, little endian size: 10505216 bytes; big endian size: 5021696 bytes
1376404 0x150094 Squashfs filesystem, little endian, version 4.0, compression:lzma, size: 5019773 bytes, 2282 inodes, blocksize: 131072 bytes, created: 2016-03-18 09:35:31
Como podemos ver, Binwalk detecta un sistema de archivos Linux. Si observamos más detenidamente la información que tenemos, podemos ver que el aviso menciona la ruta /HNAP1. Por lo tanto, podemos empezar a ver cómo el router maneja estas URLs.
Como se explica en la excelente entrada de blog de /dev/tty0, estas URLs terminan siendo manejadas por un binario llamado cgibin, ubicado en htdocs/cgibin. Pero quería comprender mejor cómo estaba configurado esto y, al final, me llevó a tener que entender cómo estaba configurado el servidor HTTP para procesar las solicitudes. Procedí a buscar el archivo httpd.conf, asumiendo que se usaba para configurar el servidor HTTP, y encontré un archivo llamado HTTP.php en la carpeta /etc/services/, que contenía, entre otras cosas, las siguientes líneas interesantes:
$httpd_conf = "/var/run/httpd.conf";
fwrite("a",$START, "xmldbc -P /etc/services/HTTP/httpcfg.php > ".$httpd_conf."\n");
fwrite("a",$START, "event PREFWUPDATE add /etc/scripts/prefwupdate.sh\n");
fwrite("a",$START, "httpd -f ".$httpd_conf."\n");
fwrite("a",$START, "event HTTP.UP\n");
fwrite("a",$START, "exit 0\n");
Supuse que en algún momento este archivo se ejecuta y escribe el archivo /var/run/httpd.conf. Para aprender cómo estaba configurado el servidor web, procedí a analizar el archivo /etc/services/HTTP/httpcfg.php (solo incluí las partes relevantes):
if ($hnap > 0)
{
echo
" Control". "\n".
" {". "\n".
" Alias /HNAP1". "\n".
" Location /htdocs/HNAP1". "\n".
" External". "\n".
" {". "\n".
" /usr/sbin/hnap { hnap }". "\n".
" }". "\n".
" IndexNames { index.hnap }". "\n".
" }". "\n";
}
¡Ahora tenemos todas las piezas! Podemos asumir que el servidor web está configurado para manejar las solicitudes HTTP a /HNAP1 mediante /usr/sbin/hnap, y según la entrada de blog de /dev/tty0 sabemos que será un enlace al binario htdocs/cgibin.
El siguiente paso fue echar un vistazo al binario cgibin para entender cómo manejaba las solicitudes HNAP. En la siguiente imagen podemos ver parte de la función main descompilada, donde la ruta URL que se le pasa se compara con diferentes rutas (como session.cgi, authentication.cgi, captcha.cgi, por nombrar algunas) hasta que se encuentra hnap y se llama a nuestra función hnap_main.
nota: Mientras analizaba la función main, tuve algunos problemas para identificar la cadena hnap, ya que Ghidra no la detectó como cadena durante el análisis inicial.
El siguiente paso era entender qué hacía esta función. Proporcionaré solo fragmentos de código de las partes relevantes que ayudarán a comprender mejor lo que hace la función; también completaré otras partes relevantes con algo de pseudocódigo —renombré algunas variables para aclarar su propósito.
...
HTTP_SOAPACTION = getenv("HTTP_SOAPACTION");
REQUEST_METHOD = getenv("REQUEST_METHOD");
HNAP_AUTH = getenv("HTTP_HNAP_AUTH");
__haystack = getenv("HTTP_COOKIE");
pcVar1 = getenv("HTTP_REFERER");