
Ce dépôt contient un article de blog sur mon analyse de CVE-2018-19987, une injection de commandes système authentifiée affectant plusieurs routeurs D-Link.
Bonjour !
Dans ce court billet de blog, je vais détailler une analyse rapide que j'ai effectuée pour mieux comprendre les détails derrière CVE-2018-19987. Au début, je me suis lancé sur cette CVE car je pensais qu'il n'existait aucune information publique ni exploit. Peu de temps après avoir effectué une première analyse, j'ai trouvé deux PoC publics sur GitHub que vous pouvez trouver ici et ici.
Comme j'avais déjà fait la majeure partie du travail et que je n'avais pas trouvé d'analyse complète, j'ai décidé d'écrire le billet de blog quand même.
J'espère qu'il vous plaira !
Mise à jour : Eh bien... après avoir cherché sur Google alors que j'étais bloqué avec ce billet de blog, j'ai trouvé l'article suivant qui décrit une vulnérabilité très similaire (CVE-2018-19986) dans le D-Link DIR-818. Série d'analyse de vulnérabilités de routeurs (5) : analyse et reproduction de la vulnérabilité d'injection de commandes CVE-2018-19986 DIR-818LW&828 (traduit par Google) par Ogur1.
J'ai commencé mon analyse en rassemblant toutes les informations possibles sur l'appareil :
Remarque : j'ai effectué toute l'analyse avec le firmware v3.01B02.
Les informations publiques sur cette vulnérabilité, disponibles sur la page de Mitre, fournissaient suffisamment d'éléments pour commencer :
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.
Pour commencer l'analyse, j'avais besoin de quelque chose à examiner, j'ai donc procédé à l'extraction du système de fichiers du routeur.
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
Comme on peut le voir, Binwalk détecte bien un système de fichiers Linux. Si l'on examine de plus près les informations dont nous disposons, on constate que l'avis de sécurité mentionne le chemin /HNAP1. Nous pouvons donc commencer à examiner comment le routeur gère ces URL.
Comme expliqué dans l'excellent billet de blog /dev/tty0, ces URL sont en fin de compte gérées par un binaire appelé cgibin, situé dans htdocs/cgibin. Mais je voulais mieux comprendre comment cela était configuré et, au final, cela m'a amené à devoir comprendre comment le serveur HTTP était configuré pour traiter les requêtes. J'ai cherché le fichier httpd.conf, en supposant qu'il était utilisé pour configurer le serveur HTTP, et j'ai trouvé un fichier appelé HTTP.php dans le dossier /etc/services/, contenant entre autres les lignes intéressantes suivantes :
$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");
J'ai supposé qu'à un moment donné ce fichier est exécuté et écrit le fichier /var/run/httpd.conf. Pour savoir comment le serveur web était configuré, j'ai procédé à l'analyse du fichier /etc/services/HTTP/httpcfg.php (je n'ai inclus que les parties pertinentes) :
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";
}
Nous avons maintenant toutes les pièces du puzzle ! On peut supposer que le serveur web est configuré pour traiter les requêtes HTTP vers /HNAP1 à l'aide de /usr/sbin/hnap, et d'après le billet de blog /dev/tty0, nous savons qu'il s'agit d'un lien vers le binaire htdocs/cgibin.
L'étape suivante consistait à examiner le binaire cgibin pour comprendre comment il gère les requêtes HNAP. Dans l'image suivante, on peut voir une partie de la fonction main décompilée, où le chemin d'URL qui lui est transmis est comparé à différents chemins (tels que session.cgi, authentication.cgi, captcha.cgi, pour n'en citer que quelques-uns) jusqu'à ce que hnap soit trouvé et que notre fonction hnap_main soit appelée.
remarque : en analysant la fonction main, j'ai eu quelques difficultés à identifier la chaîne hnap, car Ghidra ne l'a pas détectée comme chaîne lors de l'analyse initiale.
L'étape suivante consistait à comprendre ce que faisait cette fonction. Je ne fournirai que des extraits de code pour les parties pertinentes qui aideront à mieux comprendre ce que fait la fonction ; je compléterai également d'autres parties pertinentes avec du pseudocode —j'ai renommé certaines variables pour clarifier leur rôle.
...
HTTP_SOAPACTION = getenv("HTTP_SOAPACTION");
REQUEST_METHOD = getenv("REQUEST_METHOD");
HNAP_AUTH = getenv("HTTP_HNAP_AUTH");
__haystack = getenv("HTTP_COOKIE");
pcVar1 = getenv("HTTP_REFERER");