Обзор эксплоитов. Критические баги в vBulletin, InfluxDB и Django

Сегодня мы поговорим сразу о нескольких уязвимостях. Одна из них была найдена в популярном форумном движке vBulletin и позволяет атакующему выполнять произвольный код, не имея никаких прав, — от такого безобразия его отделяет лишь один POST-запрос. Также я потреплю старичка Django в поисках SQL-инъекций и покажу, как работает обход авторизации в базе данных InfluxDB.

2019 год подходит к концу, все начинают усиленно готовиться к праздникам. Безопасники добивают свои последние аудиты, которые из года в год наваливаются в эту пору. Неудивительно — ведь фискальный год тоже подходит к концу, а бюджеты еще не до конца потрачены!

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

Они просты по своей сути, однако это не мешает им иметь критический статус. Одни дают возможность выполнить произвольный код, другие — получить доступ к чувствительным данным или вовсе захватить полный доступ к системе. В этом году сильно досталось коммерческому форумному движку vBulletin: сразу несколько опасных багов было найдено в последних его версиях во второй половине года. С них и начнем.

RCE через загрузку аватара в vBulletin

Автор: Эджидио Романо (Egidio Romano aka EgiX)
Дата релиза: 4.10.2019
CVE: CVE-2019-17132
Уязвимые версии: vBulletin <= 5.5.4

Чтобы более предметно разговаривать о найденной проблеме, нужно поднять стенд и посмотреть на нее поближе. Так как vBulletin — коммерческое приложение, я предлагаю тебе самостоятельно решить, каким образом его найти.

В качестве базы данных будем использовать MySQL, а в качестве веб-сервера — докер-контейнер на основе Debian.

docker run -d -e MYSQL_USER="vb" -e MYSQL_PASSWORD="EAQhaTXieg" -e MYSQL_DATABASE="vb" --rm --name=mysql --hostname=mysql mysql/mysql-server:5.7
docker run --rm -ti --link=mysql --name=websrv --hostname=websrv -p80:80 debian /bin/bash

Устанавливаем стандартный набор из Apache2 и PHP.

apt update && apt install -y apache2 php nano php-mysqli php-xml php-gd

После этого можно запускать веб-сервер.

service apache2 start

Теперь устанавливаем vBulletin, я буду использовать версию 5.4.3.

Установка vBulletin версии 5.4.3

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

Меняем место хранения загруженных аватаров пользователей в vBulletin

Загружается аватар через отправку запроса POST на /profile/upload-profilepicture.

POST /profile/upload-profilepicture HTTP/1.1
Host: web.fh
Connection: keep-alive
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary2V36WCZNIGxAuYwu
Cookie: сессионные_куки_

---WebKitFormBoundary2V36WCZNIGxAuYwu
Content-Disposition: form-data; name="profilePhotoFile"; filename="orange_box.png#26759185"
Content-Type: image/png

содержимое_файла
---WebKitFormBoundary2V36WCZNIGxAuYwu
Content-Disposition: form-data; name="securitytoken"

CSRF-токен
---WebKitFormBoundary2V36WCZNIGxAuYwu--

Если здесь просто попытаться загрузить PHP-файл, то ничего не выйдет. Расширение файла определяется библиотекой, которая работает с картинками. Если переданный документ не будет картинкой, то скрипт просто прекратит свою работу с ошибкой not_an_image.

core/vb/library/user.php
1335:   public function uploadAvatar($filename, $crop = array(), $userid = false, $adminoverride = false)
1336:   {
...
1339:       $isImage = $imageHandler->fileLocationIsImage($filename);
1340:       if ($isImage)
1341:       {
...
1359:           $fileInfo = $imageHandler->fetchImageInfo($filename);
...
1361:       else
1362:       {
1363:           // throw something useful here.
1364:           throw new vB_Exception_Api('not_an_image');
1365:       }
...
1435:       $ext = strtolower($fileInfo[2]);
1436:
1437:       $dimensions['extension'] = empty($ext) ? $pathinfo['extension'] : $ext;
...
1485:                   'extension' => $dimensions['extension'],
...
1511:       $result = $api->updateAvatar($userid, false, $filearray, true);

После всех манипуляций вызывается updateAvatar с параметрами аватара в $filearray.

Однако существует возможность напрямую вызвать этот метод API. Чтобы это сделать, нужно отправить запрос на эндпойнт ajax/api/user/updateAvatar. Если заглянуть в тело метода updateAvatar, то можно обнаружить любопытный участок кода.

core/vb/api/user.php
4111:   public function updateAvatar($userid, $avatarid, $data = array(), $cropped = false)
4112:   {
...
4149:       if ($useavatar)
4150:       {
4151:           if (!$avatarid)
4152:           {
...
4166:               if (empty($data['extension']))
4167:               {
4168:                   $filebits = explode('.', $data['filename']);
4169:                   $data['extension'] = end($filebits);
4170:               }
4171:
4172:               $userpic->set('extension', $data['extension']);
...
4182:                   $avatarfilename = "avatar{$userid}_{$avatarrevision}.{$data['extension']}";
...
4186:                   $avatarres = @fopen("$avatarpath/$avatarfilename", 'wb');
...
4187:                   $userpic->set('filename', $avatarfilename);
4188:                   fwrite($avatarres, $data['filedata']);
4189:                   @fclose($avatarres);

Здесь расширение берется из массива $data, который можно просто передать в теле запроса. Оно будет иметь следующий вид:

userid=0&avatarid=0&data[extension]=<расширение_файла>&data[filedata]=<содержимое_файла>&securitytoken=<токен>

Когда userid установлен в ноль, скрипт выбирает текущего авторизованного пользователя, а avatarid, равный нулю, говорит, что нужно загружать аватар, а не удалять.

Вот мы и подобрались к самой сути уязвимости. vBulletin не проверят должным образом параметры data[extension] и data[fildeata], и это позволяет творить чудесные вещи. Например, установим расширение php, а в data[filedata] передадим простой PHP-код.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


aLLy: Специалист по информационной безопасности в ONsec. Research, ethical hacking and Photoshop.
Похожие материалы