Комбо для Drupal. Как захватывают сайты с Drupal, используя новые уязвимости
Форсайт
В этой статье я расскажу о двух уязвимостях в популярной CMS Drupal. Одна из них связана с архивами PHAR, не особенно страшна и работает только с правами админа, но если подключить другую (XSS), то такой дуэт становится уже очень опасным для любого сайта на незапатченном Drupal. Мы подробно разберем, как работают обе и откуда они взялись.
Итак, интересный случай XSS связан с тем, что разработчики не учли отдельные особенности работы некоторых функций PHP с кодировкой UTF-8. Из-за особенностей работы preg_replace атакующий может загрузить файл, содержащий HTML/JS-код, при переходе на который он будет выполнен в контексте браузера пользователя.
Эта уязвимость получила номер CVE-2019-6341, ее обнаружил Сэм Томас (Sam Thomas). Под угрозой оказались все версии ветки Drupal 8.6 до 8.6.13, Drupal 8.5 до 8.5.14 и Drupal 7 до 7.65.
Вторая уязвимость — unserialize при помощи архива PHAR. Отсутствие проверки пути до временной директории дает возможность использовать враппер phar://. В Drupal тоже есть возможность загрузки файлов и атакующий может загрузить картинку, содержащую полезную нагрузку. Затем указать путь до нее в качестве временной директории, используя поток phar, что приведет к выполнению произвольного кода.
Речь идет о CVE-2019-6339. Ей подвержены все версии Drupal 8.6 ниже 8.6.6, Drupal 8.5 ниже 8.5.9 и Drupal 7 ниже 7.62. Уязвимость была найдена Грегом Кнаддисоном (Greg Knaddison) из Drupal Security Team и Сэмом Томасом (Sam Thomas).
Стенд
Чтобы воспроизвести уязвимость, нам понадобятся два контейнера Docker. Первый — для сервера базы данных.
Второй — официальный, от разработчиков Drupal, с последней уязвимой к обоим багам версией — 8.6.5.
Теперь нужно пройти несложную процедуру инсталляции.

Если требуется отладка, то я по-прежнему рекомендую использовать PhpStorm и расширение Xdebug helper для браузера. Эта связка работает быстро и стабильно. Чтобы иметь возможность дебага, я дополнительно установлю PHP-расширение Xdebug.
Не забудь поменять IP-адрес 192.168.99.1 на свой и обрати внимание на путь до скомпилированной библиотеки xdebug.so. Далее нужно скачать исходники Drupal, и после перезагрузки конфигов Apache можно запускать отладчик.
После завершения установки CMS необходимо будет создать тестовую страницу или запись.

Помимо этого, для тестирования XSS понадобится любой пользователь, который сможет загружать файлы и оставлять комментарии. В дефолтной инсталляции это можно делать после прохождения регистрации. Как вариант, можешь просто создать юзера в админке.
Путь к XSS
Начнем с межсайтового скриптинга. У пользователей Drupal есть возможность комментировать записи. И, как и в любой современной CMS, в комментариях можно использовать базовую разметку. Функция, которая нам интересна, — это добавление картинок.

Причем картинка загружается с компьютера пользователя. Имена загружаемых файлов могут представлять опасность, одна из последних уязвимостей в WordPress тому пример.
За сохранение загруженных файлов отвечает функция _file_save_upload_single.
core/modules/file/file.module
Обрати внимание на аргумент $replace. Он отвечает за ситуацию, когда в директории уже присутствует файл с таким же именем, как у загружаемого. По дефолту новый файл переименовывается.
core/includes/file.inc
Функция file_create_filename генерирует новое имя для загружаемого файла. Но перед этим производится санитизация названия. Убираются все нежелательные символы.
core/includes/file.inc
Функция preg_replace заменяет на символ подчеркивания (_) все символы с ASCII-кодом до 31 (1F). И все бы ничего, если бы не PCRE-модификатор u (PCRE_UTF8). Он интерпретирует входные данные как строку UTF-8. Допустимая длина символа UTF-8 — от одного до четырех байт. UTF-8 спроектирован с учетом обратной совместимости с набором символов ASCII. Поэтому в диапазоне однобайтовых кодов (0x00—0x7F) ASCII и UTF-8 пересекаются.
При работе функций preg_* с этой кодировкой есть небольшая особенность: если переданная строка имеет некорректный формат, то результатом работы будет NULL, а выполнение кода продолжится. Об этом четко написано в документации — см. справку по модификатору u.
Чтобы понять, как сделать строку невалидной, обратимся к спецификации по UTF-8 — RFC 3629. Согласно этому документу, байты C0, C1, F5-FF никогда не должны присутствовать в корректной строке в кодировке UTF-8.

Вооружившись этой информацией, я накидал тестовый скрипт, чтобы проверить особенность обработки невалидных строк.
test.php

Теперь посмотрим, что произойдет, когда мы передадим некорректную строку UTF-8. Для этого нам нужно сначала загрузить файл с кодом символа \xFF в качестве названия и одним из разрешенных расширений, я использовал png.

Я поставил брейк-пойнт на функцию file_create_filename и повторил запрос. В самом начале происходит вызов preg_replace, после которого переменная $basenameстановится null.

core/includes/file.inc
Дальше формируется полный путь до загруженного файла ($destination), а так как $basename у нас пустая, то путь будет указывать просто на папку с загруженными файлами. Разумеется, эта директория существует, поэтому дальше мы попадаем в тело условия.

core/includes/file.inc
Тут к имени файла добавляется суффикс, по сути, это просто счетчик.
core/includes/file.inc

В итоге получается путь /var/www/html/sites/default/files/inline-images/_0. По нему и сохраняется содержимое файла.

Как же это можно использовать?
Сервер возвращает содержимое загруженных файлов без заголовка Content-Type, а современные браузеры пытаются определить тип содержимого автоматически. Такое поведение и позволит провести XSS-атаку. Для этого достаточно загрузить код на JS.
Дальше используем еще одну возможность в комментариях — ссылки. Формируем ссылку на загруженный файл с XSS.
Теперь администратору достаточно перейти по ссылке, и код будет выполнен.

Кстати, необязательно использовать загрузку картинок через комментарии. Для тех же целей можно приспособить назначение аватара в профиле пользователя. В этом случае путь будет немного другим: /sites/default/files/pictures/<ГГГГ-ММ>/.
Выполняем произвольный код
Давай теперь посмотрим на другую проблему — PHAR-десериализацию. В панели администратора есть раздел File system. Там можно задать папку для хранения временных файлов (Temporary directory), а также время их жизни.

При сохранении данных формы срабатывает функция system_check_directory, которая проверяет существование указанной директории и наличие необходимых прав на нее.
core/modules/system/system.module
Здесь отсутствует проверка введенных данных и можно указать враппер phar:// и путь до PHAR-архива с полезной нагрузкой. Любая функция, которая поддерживает работу с потоками, может триггерить уязвимость.
Для генерации я воспользуюсь классной утилитой phpggc. В ней есть джентльменский набор гаджетов для популярных фреймворков, CMS и различных библиотек. Список постоянно пополняется.
Drupal использует библиотеку Guzzle, в которой есть известная цепочка гаджетов, ведущая к RCE.

/drupal-8.6.5/vendor/guzzlehttp/guzzle/CHANGELOG.md
Выбираем гаджет Guzzle/RCE1. Здесь ты сразу можешь сгенерировать архив PHAR, указав ключ -p и имя архива через -o.
Далее переименовываем rce.phar в rce.png и загружаем на сервер как картинку. Если теперь указать путь до файла через враппер PHAR в качестве временной папки, то пейлоад отработает и код выполнится.


Объединяем атаки в цепочку
Чтобы получить окончательный вектор атаки, нужно объединить XSS и RCE от администратора.
Сначала загружаем архив PHAR с полезной нагрузкой как PNG и запоминаем путь до него. Далее нам понадобится код на JavaScript, который будет подгружать от админа страницу с настройками и отправлять форму. В ней в качестве file_temporary_path будет указан путь до архива.
Скрипт должен будет получать ответ от сервера. Чтобы легче его парсить, я поместил результат работы команды ls -l в тег <shell>.
poc.html
Загружаем этот код через первую уязвимость. Теперь остается только заставить администратора перейти по нужной ссылке. Но это уже совсем другая история.

Демонстрация уязвимости (видео)
Выводы
В статье я рассмотрел очередную интересную цепочку, ведущую от XSS к RCE. Таких вариантов атаки в последнее время встречается все больше.
На данный момент обе уязвимости были исправлены. Чтобы исключить возможность использования враппера phar и архивов PHAR, команда разработки Drupal добавила кастомные обработчики для потоков phar://.

XSS продержалась немного дольше, и патч для нее вышел в марте этого года. Разработчики добавили компонент Unicode, который имеет метод validateUtf8 для проверки строки на соответствие стандартам UTF-8. Так что следи за новостями безопасности и вовремя обновляй свои продукты!