Содержание статьи
INFO
Уязвимость связана с техникой PHAR-десериализации, которую мы недавно освещали. Баг актуален для phpBB версии 3.2.3.
Думаю, что phpBB в представлении не нуждается. Он существует аж с 16 декабря 2000 года. Поднимем бокалы за его совершеннолетие! За это время движок повидал множество уязвимостей самого разного рода. Одна из самых известных — CVE-2004-1315, или в миру viewtopic highlight PHP injection. Этот баг был одним из первых, который я изучил.
Впрочем, вернемся к современным реалиям. Сейчас phpBB, конечно, растерял былую популярность, но все еще огромное количество площадок выбирает его как основную платформу общения пользователей.
Уязвимость нашел исследователь из RIPS Technologies, воспользовавшись сканером исходных кодов производства своей компании. Можно даже заценить его отчет.
Стенд
Начнем с привычного — поднятия среды для тестирования уязвимости. Форум работает со многими базами данных, но я буду использовать старый добрый MySQL в виде контейнера Docker. Рекомендую использовать версии из ветки 5.х, так как в последних бранчах (8.х) изменился протокол авторизации по умолчанию и клиентские библиотеки текущих репозиториев PHP не работают с ним. Такое поведение можно поменять в конфигурационном файле MySQL, но зачем лишние телодвижения для тестового стенда, верно?
$ docker run -e MYSQL_USER="phpbb" -e MYSQL_PASSWORD="JaLdqX5on0" -e MYSQL_DATABASE="phpbb" -d --rm --name=mysql --hostname=mysql mysql/mysql-server:5.7
Теперь можно приступать к разворачиванию самого сервера. По традиции использую Debian.
$ docker run --rm -p80:80 -ti --name=phpbb --hostname=phpbb --link=mysql debian /bin/bash
Обновляем репозитории и ставим нужные пакеты.
$ apt update && apt install -y zip wget nano apache2 php php-mysql php-xml php-mbstring php-gd
Скачиваем архив с уязвимой версией форума phpBB (3.2.3) и распаковываем его.
$ cd /var/www/html
$ wget https://www.phpbb.com/files/release/phpBB-3.2.3.zip
$ unzip phpBB-3.2.3.zip
$ chown www-data:root -R phpBB3
Если хочется побаловаться с отладкой, то дополнительно ставим xdebug
.
$ apt install -y php-xdebug
Настраиваем модуль и включаем его. Не забывай изменить IP под свои реалии.
$ echo "xdebug.remote_enable=1" >> /etc/php/7.0/mods-available/xdebug.ini
$ echo "xdebug.remote_host=192.168.99.1" >> /etc/php/7.0/mods-available/xdebug.ini
$ phpenmod xdebug
Далее правим конфиги веб-сервера и запускаем его.
$ sed 's/html/html\/phpBB3/' -i /etc/apache2/sites-enabled/000-default.conf
$ service apache2 start
Теперь переходим в браузере по адресу контейнера и устанавливаем и настраиваем форум.
После завершения инсталляции не забудь снести папку install
.
$ rm -rf /var/www/html/phpBB3/install
Стенд готов.
Часть первая: внедряем враппер phar
Начнем с просмотра исходников. Если ты внимательно изучал мою прошлую статью про PHAR-десериализацию, то знаешь, на вызовы каких функций стоит обратить особое внимание при поиске потенциально уязвимых мест. Конкретно в этом случае нужно поискать file_exists
. Код phpBB объемный (~300 тысяч строк), и вызовов этой функции там предостаточно. Но нас интересуют только те, которым в качестве аргумента можно пропихнуть юзердату. Не буду тянуть и скажу, что интересующий нас вызов находится в файле functions_acp.php
.
/phpBB3.2.3/includes/functions_acp.php
420: function validate_config_vars($config_vars, &$cfg_array, &$error)
421: {
...
428: foreach ($config_vars as $config_name => $config_definition)
429: {
...
443: switch ($validator[$type])
444: {
...
544: case 'rpath':
545: case 'rwpath':
...
568: case 'absolute_path':
569: case 'absolute_path_writable':
570: // Path being relative (still prefixed by phpbb_root_path), but with the ability to escape the root dir...
571: case 'path':
572: case 'wpath':
...
588: $path = in_array($config_definition['validate'], array('wpath', 'path', 'rpath', 'rwpath')) ? $phpbb_root_path . $cfg_array[$config_name] : $cfg_array[$config_name];
589:
590: if (!file_exists($path))
591: {
592: $error[] = sprintf($user->lang['DIRECTORY_DOES_NOT_EXIST'], $cfg_array[$config_name]);
593: }
594:
595: if (file_exists($path) && !is_dir($path))
596: {
597: $error[] = sprintf($user->lang['DIRECTORY_NOT_DIR'], $cfg_array[$config_name]);
598: }
599:
600: // Check if the path is writable
601: if ($config_definition['validate'] == 'wpath' || $config_definition['validate'] == 'rwpath' || $config_definition['validate'] === 'absolute_path_writable')
602: {
603: if (file_exists($path) && !$phpbb_filesystem->is_writable($path))
604: {
605: $error[] = sprintf($user->lang['DIRECTORY_NOT_WRITABLE'], $cfg_array[$config_name]);
606: }
607: }
Из названия файла можно понять, что функция валидации конфигурационных переменных (validate_config_vars
) заходит в нужную нам ветку, когда выполняется проверка путей в панели администратора (в терминологии phpBB ACP — Administrator Control Panel).
Проверим это. Откроем админку и найдем любой раздел, где можно указать путь.
Как видишь, я открыл настройки прикрепленных файлов. Там есть опция Upload directory — папка, в которую они будут загружаться. Теперь поставим бряк где-нибудь в начале тела case
и нажмем Submit.
Брейк-пойнт сработал, так как валидатором переменной upload_path
служит wpath
.
/phpBB3.2.3/includes/acp/acp_attachments.php
138: $display_vars = array(
...
150: 'upload_path' => array('lang' => 'UPLOAD_DIR', 'validate' => 'wpath', 'type' => 'text:25:100', 'explain' => true),
Никаких дополнительных проверок переменной $path
не производится, и указанное пользователем значение попадает в качестве аргумента в функцию file_exists
.
Обрати внимание на добавленный префикс ./../
. Он появляется, потому что мы имеем дело с настройкой, которая подразумевает относительные пути. Но для выполнения атаки нам нужен полный контроль над всей переменной, поскольку требуется передать значение, начинающееся с враппера phar://
. Для этих целей отлично подойдут те настройки, у которых есть валидатор absolute_path
.
Одна из таких — это img_imagick
. Путь до бинарника утилиты ImageMagick для манипуляции с загруженными изображениями. Находится она там же, в разделе настройки аттачей.
/phpBB3.2.3/includes/acp/acp_attachments.php
138: $display_vars = array(
...
167: 'img_imagick' => array('lang' => 'IMAGICK_PATH', 'validate' => 'absolute_path', 'type' => 'text:20:200', 'explain' => true, 'append' => ' <span>[ <a href="' . $this->u_action . '&action=imgmagick">' . $user->lang['SEARCH_IMAGICK'] . '</a> ]</span>'),
Вот теперь получается настоящее внедрение, и первая часть атаки успешно выполнена.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»