
Этот репозиторий содержит пост в блоге о моём анализе CVE-2018-19987 — аутентифицированной инъекции команд ОС, затрагивающей несколько маршрутизаторов D-Link.
Всем привет!
В этой короткой записи в блоге я разберу быстрый анализ, который я провел, чтобы лучше понять детали CVE-2018-19987. Сначала я занялся этой CVE, потому что думал, что нет никакой публичной информации или эксплойта. Вскоре после первого анализа я нашел два публичных PoC на GitHub, которые можно найти здесь и здесь.
Так как я уже сделал большую часть работы и не нашел полного анализа, я решил всё же написать пост в блоге.
Надеюсь, вам понравится!
Обновление: Ну... после гугления, когда я застрял с этим постом, я нашел следующую статью, описывающую очень похожую уязвимость (CVE-2018-19986) в D-Link DIR-818. Анализ уязвимостей маршрутизаторов (серия 5): CVE-2018-19986 DIR-818LW&828 анализ и воспроизведение уязвимости внедрения команд (перевод Google) от Ogur1.
Я начал свой анализ со сбора всей возможной информации об устройстве:
Примечание: Весь анализ я проводил с прошивкой v3.01B02.
Публичная информация об этой уязвимости, доступная на странице Mitre, предоставила достаточно информации для начала:
Устройства 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 и DIR-890L Rev.A 1.21B02_BETA неправильно обрабатывают IsAccessPoint в /HNAP1/SetAccessPointMode. В исходном коде SetAccessPointMode.php параметр IsAccessPoint сохраняется в файле скрипта ShellPath без какой-либо проверки регулярных выражений. После выполнения файла скрипта происходит внедрение команд. Уязвимое XML-сообщение /HNAP1/SetAccessPointMode может содержать метасимволы оболочки в элементе IsAccessPoint, такие как строка `telnetd`.
Для начала анализа мне нужно было что-то изучить, поэтому я приступил к извлечению файловой системы маршрутизатора.
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
Как видим, Binwalk обнаруживает файловую систему Linux. Если присмотреться к имеющейся информации, то можно заметить, что в бюллетене упоминается путь /HNAP1. Итак, мы можем начать с изучения того, как маршрутизатор обрабатывает эти URL-адреса.
Как объясняется в отличном посте в блоге /dev/tty0, эти URL-адреса в конечном итоге обрабатываются бинарным файлом cgibin, расположенным в htdocs/cgibin. Но я хотел лучше понять, как это настроено, и в итоге мне пришлось разобраться, как настроен HTTP-сервер для обработки запросов. Я начал искать имя файла httpd.conf, предполагая, что он используется для настройки HTTP-сервера, и нашел файл с именем HTTP.php в папке /etc/services/, содержащий, среди прочего, следующие интересные строки:
$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");
Я предположил, что в какой-то момент этот файл выполняется и записывает файл /var/run/httpd.conf. Чтобы узнать, как настроен веб-сервер, я проанализировал файл /etc/services/HTTP/httpcfg.php (я включил только соответствующие части):
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";
}
Теперь у нас есть все фрагменты! Можно предположить, что веб-сервер настроен на обработку HTTP-запросов к /HNAP1 с помощью /usr/sbin/hnap, и, основываясь на посте /dev/tty0, мы знаем, что это будет ссылка на бинарный файл htdocs/cgibin.
Следующим шагом было изучение бинарного файла cgibin, чтобы понять, как он обрабатывает запросы HNAP. На следующем изображении мы видим часть декомпилированной функции main, где переданный путь URL сравнивается с различными путями (такими как session.cgi, authentication.cgi, captcha.cgi и другими), пока не будет найден hnap и не будет вызвана наша функция hnap_main.
примечание: При анализе функции main у меня возникли некоторые проблемы с идентификацией строки hnap, так как Ghidra не определил её как строку во время начального анализа.
Следующим шагом было понять, что делает эта функция. Я предоставлю только фрагменты кода для соответствующих частей, которые помогут лучше понять, что делает функция, а также дополню другие соответствующие части псевдокодом — я переименовал некоторые переменные для ясности их назначения.
...
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 {
// Эти действия будут происходить во время аутентификации. Процесс
if ("GetCAPTCHAsetting" in HTTP_SOAPACTION) {
sess_generate_captcha();
} else {
if ("Login" in HTTP_SOAPACTION) {
perform_login();
}
// Мы попадем сюда после аутентификации.
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();
// Если мы аутентифицированы и НЕ пытаемся выйти
// код попытается выполнить запрошенное действие
} else {
// Если мы попали сюда, мы победили
// интересный код ниже
goto LAB_004141d4;
}
}
}
}
// Если мы не аутентифицированы, мы ничего не можем сделать
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") {
// Здесь извлекаются аргументы для PHP
parse_request_and_extract_xml()
if (hnap_action == "GetFirmwareStatus") {
system("sh /etc/events/checkfw.sh > /dev/console");
}
// Здесь формируются окончательные аргументы для xmldbc_ephp
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 выполняется (в нашем случае SetAccessPointMode.php) и shell
// скрипт записывается в ShellPath
xmldbc_ephp(0,0,ARGS_FOR_XMLDBC_PHP,stdout);
snprintf(hnap_action, 0x100, "%s", hnap_action);
// Строится команда оболочки для выполнения ранее записанного shell файла
// (Файл, записанный PHP-скриптом)
shell_command = "sh %s%s.sh > /dev/console &";
}
snprintf(cmd_to_execute, 0x100, shell_command, &PATH, hnap_action);
// Файл выполняется, содержащий внедрение команд
system(cmd_to_execute);
}
...
Завершив анализ этой функции, я решил взглянуть на PHP-файл SetAccessPointMode.php — который в итоге и содержал ошибку — и попытался собрать все части вместе:
...
$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");
...
Как видим, у нас есть переменная $ShellPath, которая заполняется функцией hnap_main, и переменная $IsAccessPoint, контролируемая пользователем и передаваемая в XML-запросе. С помощью этого мы можем подтвердить, как происходит внедрение команд ОС в переменную, записываемую PHP-файлом. Здесь вы можете найти полный PoC, разработанный pr0v3rbs для эксплуатации этой проблемы.
Позже мы обсудим немного больше о затронутых и исправленных версиях. Как вы можете видеть в таблице ниже, похоже, что в патче произошла некая регрессия, которая повторно ввела эту уязвимость в версиях, которые уже должны были быть исправлены.
(*) Я не проверял эти версии прошивок, так как их незашифрованные аналоги были уязвимы.
Как видно из таблицы выше, уязвимость была исправлена в некоторых версиях прошивок путем удаления затронутого PHP-файла SetAccessPointMode.php. Когда файл отсутствует, проверки, выполняемые в функции, завершаются ошибкой, и ничего не выполняется. Я подтвердил это, проанализировав функцию hnap_function в версиях прошивок FW v3.131 и 3.15B02 WW; код для hnap_main не изменился по отношению к этой проблеме, но PHP-файла больше не было.
Что еще более интересно, так это то, что каким-то образом эта ошибка была повторно введена в версиях прошивок FW v3.11 вплоть до FW v3.12B04 после того, как была исправлена в версии 3.11B01_icjg_WW.
В качестве первого вывода я бы сказал: никогда не доверяйте этим устройствам как по-настоящему безопасным — то, что произошло с обновлениями, является ярким примером того, почему не стоит этого делать. Также после проведенного анализа я могу заключить, что первый PoC, перечисленный в этом посте, не будет работать для этой конкретной версии, так как мы увидели, что для выполнения уязвимого PHP-файла необходимо выполнить некоторые условия.
И самый важный вывод: будьте очень осторожны при определении критичности уязвимости на основе ее оценки CVSS. Я решил проанализировать эту CVE, поскольку она имела оценку 9.8 (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), и очевидно, что, по крайней мере для этой версии, злоумышленнику потребуются учетные данные для ее эксплуатации.
В качестве следующих шагов я буду работать над возможностью эмулировать этот бинарный файл для эксплуатации этой уязвимости без доступа к самому маршрутизатору, но это будет материалом для другого поста!
Спасибо за чтение.
| Прошивка | Версия | MD5 хэш | Дата релиза | Уязвима |
|---|
| DIR822C1_FW315WWb02.bin | 3.15B02 WW | 7121771c3e1706ba76fbf244023efad3 | 06.11.2019 | НЕТ |
| DIR822C1_FW313WWb01.bin | FW v3.13 | aa16c7016f67be384e0784e439ce26d2 | 10.07.2019 | НЕТ |
| DIR822C1_FW303WWb04_i4sa_middle.bin | FW v3.12B04 | c3b9a3f115c02e739690616aba2f2d99 | 26.04.2019 | ДА |
| DIR822C1_FW312WWb04.bin | FW v3.12B04 | eb11afbd136a5b29cea18141f727bfa8 | 26.04.2019 | ДА (*) |
| DIR822C1_FW311WWb01.bin | FW v3.11 | 6d7c90eaaae835667faea65c862b3c82 | 01.01.2019 | ДА |
| DIR822C1_FW311bWWb01_icjg.bin | 3.11B01_icjg_WW | 75e361e1465604aeda5d5dbcaecca977 | 21.12.2018 | НЕТ |
| DIR822C1_FW303WWb04_i4sa_middle.bin | FW v3.10B06 | c3b9a3f115c02e739690616aba2f2d99 | 17.08.2018 | ДА |
| DIR822C1_FW310WWb06.bin | FW v3.10B06 | e33db75d0801fddb1c90308982e69fe5 | 17.08.2018 | ДА (*) |
| DIR-822_C1_FW302WWb05.bin | FW v3.02 | 0dbf840c0ff5d3a5b593d33690e15d82 | 14.09.2017 | ДА |
| DIR822C1_FW301WWb02.bin | FW v3.01B02 | 1bff7ec8b4da0643f65b4d44c630e92b | 27.04.2016 | ДА |