
Dieses Repo enthält einen Blogbeitrag über meine Analyse zu CVE-2018-19987, einer authentifizierten OS-Befehlsinjektion, die mehrere D-Link-Router betrifft.
Hallo zusammen!
In diesem kurzen Blogbeitrag stelle ich eine schnelle Analyse vor, die ich durchgeführt habe, um die Details hinter CVE-2018-19987 besser zu verstehen. Zunächst bin ich dieser CVE nachgegangen, weil ich dachte, dass es keine öffentlichen Informationen oder Exploits dazu gäbe. Kurz nach einer ersten Analyse fand ich zwei öffentliche PoCs auf GitHub, die ihr hier und hier finden könnt.
Da ich den Großteil der Arbeit bereits erledigt hatte und keine vollständige Analyse gefunden hatte, beschloss ich, den Blogbeitrag trotzdem zu schreiben.
Ich hoffe, er gefällt euch!
Aktualisierung: Nun ja, nachdem ich gegoogelt hatte, während ich an diesem Blogbeitrag festhing, fand ich den folgenden Artikel, der eine sehr ähnliche Schwachstelle (CVE-2018-19986) im D-Link DIR-818 beschreibt. Router-Schwachstellenanalyse-Serie (5): CVE-2018-19986 DIR-818LW&828 Command-Injection-Schwachstellenanalyse und -Reproduktion (Google-Übersetzung) von Ogur1.
Ich begann meine Analyse, indem ich alle verfügbaren Informationen über das Gerät sammelte:
Hinweis: Ich habe die gesamte Analyse mit der Firmware v3.01B02 durchgeführt.
Öffentliche Informationen zu dieser Schwachstelle, verfügbar auf Mitres Seite, lieferten genügend Informationen für den Einstieg:
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.
Um die Analyse zu beginnen, brauchte ich etwas, das ich untersuchen konnte, also extrahierte ich das Dateisystem des Routers.
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
Wie wir sehen können, erkennt Binwalk ein Linux-Dateisystem. Wenn wir die vorhandenen Informationen genauer betrachten, sehen wir, dass das Advisory den Pfad /HNAP1 erwähnt. Wir können also damit beginnen zu untersuchen, wie der Router diese URLs verarbeitet.
Wie in dem exzellenten /dev/tty0-Blogbeitrag erklärt, werden diese URLs letztendlich von einer Binärdatei namens cgibin verarbeitet, die sich unter htdocs/cgibin befindet. Ich wollte jedoch besser verstehen, wie dies konfiguriert war, und das führte mich schließlich dazu, verstehen zu müssen, wie der HTTP-Server konfiguriert war, um Anfragen zu verarbeiten. Ich suchte nach dem Dateinamen httpd.conf und nahm an, dass er zur Konfiguration des HTTP-Servers verwendet wird. Dabei fand ich eine Datei namens HTTP.php im Ordner /etc/services/, die unter anderem die folgenden interessanten Zeilen enthielt:
$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");
Ich nahm an, dass diese Datei irgendwann ausgeführt wird und die Datei /var/run/httpd.conf schreibt. Um zu erfahren, wie der Webserver konfiguriert war, analysierte ich die Datei /etc/services/HTTP/httpcfg.php (ich habe nur die relevanten Teile aufgenommen):
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";
}
Jetzt haben wir alle Puzzleteile! Wir können annehmen, dass der Webserver so konfiguriert ist, dass er HTTP-Anfragen an /HNAP1 mithilfe von /usr/sbin/hnap verarbeitet, und basierend auf dem /dev/tty0-Blogbeitrag wissen wir, dass es sich um einen Link auf die Binärdatei htdocs/cgibin handelt.
Der nächste Schritt war, die Binärdatei cgibin zu untersuchen, um zu verstehen, wie sie HNAP-Anfragen verarbeitet. Im folgenden Bild sehen wir einen Teil der dekompilierten main-Funktion, in der der übergebene URL-Pfad mit verschiedenen Pfaden verglichen wird (z. B. session.cgi, authentication.cgi, captcha.cgi, um nur einige zu nennen), bis hnap gefunden wird und unsere Funktion hnap_main aufgerufen wird.
Hinweis: Bei der Analyse der main-Funktion hatte ich einige Probleme, den hnap-String zu identifizieren, da Ghidra ihn während der ersten Analyse nicht als String erkannt hatte.
Der nächste Schritt war zu verstehen, was diese Funktion tut. Ich werde nur Codeausschnitte für die relevanten Teile bereitstellen, die helfen, besser zu verstehen, was die Funktion tut. Außerdem werde ich andere relevante Teile mit etwas Pseudocode vervollständigen — ich habe einige Variablen umbenannt, um ihren Zweck zu verdeutlichen.
...
HTTP_SOAPACTION = getenv("HTTP_SOAPACTION");
REQUEST_METHOD = getenv("REQUEST_METHOD");
HNAP_AUTH = getenv("HTTP_HNAP_AUTH");
__haystack = getenv("HTTP_COOKIE");
pcVar1 = getenv("HTTP_REFERER");
...
if (HTTP_SOAPACTION != "") {
if (HTTP_SOAPACTION == GetDeviceSettings) {
...
} else {
// These actions will occur during auth. Process
if ("GetCAPTCHAsetting" in HTTP_SOAPACTION) {
sess_generate_captcha();
} else {
if ("Login" in HTTP_SOAPACTION) {
perform_login();
}
// We'll land here once auth.
if (HNAP_AUTH != "") {
if ("uid=" in HTTP_COOKIE){
is_valid_auth = perform_auth_process()
if (is_valid_auth) {
if("logout" in HTTP_SOAPACTION){
perform_logout();
// If we are auth. and NOT trying to logout
// the code will try to perform the action
// we requested
} else {
// If we arrive here we win
// interesting code below
goto LAB_004141d4;
}
}
}
}
// If we are not authenticated we can't do anything
Return "You need proper authorization to use this resource"
}
}
} else {
...
}
LAB_004141d4:
hnap_action = get_hnap_operation(HTTP_SOAPACTION);
if (HTTP_SOAPACTION != "") {
hnap_action_len = strlen(hnap_action);
}
snprintf(path_to_hnap_php_file,0x100,"%s/%s.php","/etc/templates/hnap/",hnap_action);
if (!check_file_access(path_to_hnap_php_file)){
return "HNAP ACTION DOES NOT EXIST (FAIL)"
}
if (REQUEST_METHOD == "POST") {
// Here arguments for the PHP are extracted
parse_request_and_extract_xml()
if (hnap_action == "GetFirmwareStatus") {
system("sh /etc/events/checkfw.sh > /dev/console");
}
// Here the final arguments for the xmldbc_ephp are crafted
snprintf(ARGS_FOR_XMLDBC_PHP,0x100,"%s%s.php\nShellPath=%s%s.sh\nPrivateKey=%s\n",
"/etc/templates/hnap/", hnap_action, &ShellPath, hnap_action, &PRIVATE_KEY);
// PHP is executed (in our case SetAccessPointMode.php) and the shell
// script is written to ShellPath
xmldbc_ephp(0,0,ARGS_FOR_XMLDBC_PHP,stdout);
snprintf(hnap_action, 0x100, "%s", hnap_action);
// Shell command is built to run the previously written shell file
// (File written by the PHP script)
shell_command = "sh %s%s.sh > /dev/console &";
}
snprintf(cmd_to_execute, 0x100, shell_command, &PATH, hnap_action);
// File is executed containing the command injection
system(cmd_to_execute);
}
...
Nachdem die Analyse dieser Funktion abgeschlossen war, beschloss ich, einen Blick auf die PHP-Datei SetAccessPointMode.php zu werfen — die letztendlich die Datei mit dem Fehler war — und versuchte, alle Puzzleteile zusammenzusetzen:
...
$IsAccessPoint = query("/runtime/hnap/SetAccessPointMode/IsAccessPoint");
...
fwrite("w",$ShellPath, "#!/bin/sh\n");
fwrite("a",$ShellPath, "echo [$0] $1 ... > /dev/console\n");
fwrite("a",$ShellPath, "echo IsAccessPoint = ".$IsAccessPoint." > /dev/console\n");
fwrite("a",$ShellPath, "echo Result = ".$Result."\n");
...
Wie wir sehen können, haben wir unsere Variable $ShellPath, die von der Funktion hnap_main gefüllt wird, und die Variable $IsAccessPoint, die vom Benutzer gesteuert und in der gesendeten XML-Anfrage übergeben wird. Damit können wir bestätigen, wie eine OS-Command-Injection in der von der PHP-Datei geschriebenen Variable ausgeführt wird. Hier findet ihr einen vollständigen, von pr0v3rbs entwickelten PoC, um dieses Problem auszunutzen.
Wir werden später noch etwas mehr über die betroffenen und behobenen Versionen sprechen. Wie ihr in der folgenden Tabelle sehen könnt, scheint es eine Art Regression mit dem Patch gegeben zu haben, die diese Schwachstelle in Versionen wieder einführte, die eigentlich bereits gepatcht sein sollten.
(*) Ich habe diese Firmware-Versionen nicht überprüft, da ihre unverschlüsselten Gegenstücke verwundbar waren.
Wie wir in der obigen Tabelle sehen können, wurde die Schwachstelle in einigen Firmware-Versionen behoben, indem die betroffene PHP-Datei SetAccessPointMode.php entfernt wurde. Sobald die Datei nicht mehr existiert, schlagen die in der Funktion durchgeführten Prüfungen fehl und es wird nichts ausgeführt. Ich habe dies durch die Analyse der hnap_function in den Firmware-Versionen FW v3.131 und 3.15B02 WW bestätigt; der Code für hnap_main hat sich in Bezug auf dieses Problem nicht geändert, aber die PHP-Datei war nicht mehr vorhanden.
Noch interessanter ist, dass dieser Fehler irgendwie in den Firmware-Versionen FW v3.11 bis FW v3.12B04 wieder eingeführt wurde, nachdem er in Version 3.11B01_icjg_WW behoben worden war.
Als erste Schlussfolgerung würde ich sagen: Betrachtet diese Geräte niemals als wirklich sichere Geräte — was mit den Updates passiert ist, ist ein klares Beispiel dafür, warum ihr das nicht tun solltet. Außerdem kann ich nach der durchgeführten Analyse schlussfolgern, dass der erste in diesem Blogbeitrag aufgeführte PoC für diese spezifische Version nicht funktionieren wird, da wir gesehen haben, dass bestimmte Bedingungen erfüllt sein müssen, um die verwundbare PHP-Datei auszuführen.
Und die wichtigste Schlussfolgerung: Seid sehr vorsichtig, wenn ihr entscheidet, wie kritisch eine Schwachstelle basierend auf ihrem CVSS-Score ist. Ich habe mich entschieden, diese CVE zu analysieren, da sie mit 9.8 bewertet wurde (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), und es ist klar, dass der Angreifer, zumindest für diese Version, Zugangsdaten benötigt, um sie ausnutzen zu können.
In den nächsten Schritten werde ich daran arbeiten, diese Binärdatei emulieren zu können, um diese Schwachstelle auszunutzen, ohne Zugriff auf den Router selbst zu haben, aber das wird Material für einen weiteren Beitrag sein!
Danke fürs Lesen.
| Firmware | Version | MD5-Hash | Veröffentlichungsdatum | Verwundbar |
|---|
| DIR822C1_FW315WWb02.bin | 3.15B02 WW | 7121771c3e1706ba76fbf244023efad3 | 06/11/2019 | NEIN |
| DIR822C1_FW313WWb01.bin | FW v3.13 | aa16c7016f67be384e0784e439ce26d2 | 10/07/2019 | NEIN |
| DIR822C1_FW303WWb04_i4sa_middle.bin | FW v3.12B04 | c3b9a3f115c02e739690616aba2f2d99 | 26/04/2019 | JA |
| DIR822C1_FW312WWb04.bin | FW v3.12B04 | eb11afbd136a5b29cea18141f727bfa8 | 26/04/2019 | JA (*) |
| DIR822C1_FW311WWb01.bin | FW v3.11 | 6d7c90eaaae835667faea65c862b3c82 | 01/01/2019 | JA |
| DIR822C1_FW311bWWb01_icjg.bin | 3.11B01_icjg_WW | 75e361e1465604aeda5d5dbcaecca977 | 21/12/2018 | NEIN |
| DIR822C1_FW303WWb04_i4sa_middle.bin | FW v3.10B06 | c3b9a3f115c02e739690616aba2f2d99 | 17/08/2018 | JA |
| DIR822C1_FW310WWb06.bin | FW v3.10B06 | e33db75d0801fddb1c90308982e69fe5 | 17/08/2018 | JA (*) |
| DIR-822_C1_FW302WWb05.bin | FW v3.02 | 0dbf840c0ff5d3a5b593d33690e15d82 | 14/09/2017 | JA |
| DIR822C1_FW301WWb02.bin | FW v3.01B02 | 1bff7ec8b4da0643f65b4d44c630e92b | 27/04/2016 | JA |