
Routes de bootstrap de terminal réservées aux admins, vérifiées uniquement pour l'état de connexion, qui permettent à un membre normal de l'équipe de piloter le backend de terminal en temps réel de Coolify et d'exécuter des commandes sur les serveurs de l'équipe.
Les routes d'amorçage du terminal réservé aux administrateurs ne vérifiaient que l'état de connexion, permettant à un membre normal de l'équipe de piloter le backend temps réel de Coolify et d'exécuter des commandes sur les serveurs de l'équipe.
J'ai découvert ce problème en examinant Coolify, un PaaS open-source auto-hébergé, avec une question de sécurité très directe en tête :
L'accès au terminal est-il réellement appliqué à la frontière de confiance du backend, ou seulement dans l'interface utilisateur ?
Dans ce cas, la réponse était mauvaise.
Coolify avait l'intention de restreindre l'accès au terminal aux administrateurs et propriétaires d'équipe, mais les routes d'amorçage du terminal en temps réel ne vérifiaient que si l'utilisateur était connecté. Cela permettait à un membre d'équipe à faibles privilèges de satisfaire les vérifications de confiance du websocket du terminal et d'atteindre l'exécution de commandes sur les serveurs de l'équipe.
J'ai validé cela de bout en bout dans un laboratoire local construit à partir de la révision vulnérable et je l'ai ensuite signalé en privé. Le problème a été assigné CVE-2026-34048 avec :
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Coolify : Coolify sur GitHub
CVE : CVE-2026-34048
Cela affectait Coolify, un PaaS open-source auto-hébergé. Sur son site officiel, Coolify déclare avoir 3 641+ clients cloud, et se présente comme une plateforme pour déployer des sites web, des bases de données, des applications web et 280+ services en un clic. Son journal des modifications officiel v4.0 indique également que des milliers d'entreprises et de personnes utilisent Coolify en production depuis 1 à 2 ans.
photo0
session de membre d'équipe à faibles privilèges -> /terminal/auth et /terminal/auth/ips ne vérifient que l'état de connexion -> le websocket temps réel se fie à ces réponses -> le membre énumère le serveur de l'équipe et l'UUID de clé SSH visible -> /terminal/ws accepte la session -> un PTY basé sur SSH est créé -> accès shell sur le serveur de l'équipe
Coolify est un PaaS auto-hébergé et une plateforme de déploiement.
Il gère :
Cette dernière capacité est celle qui est importante ici.
Dès qu'une plateforme peut ouvrir des terminaux vers des hôtes gérés, son modèle d'autorisation n'est plus seulement une logique applicative. Cela devient une frontière de confiance de l'infrastructure.
La question importante n'était pas de savoir si la page /terminal semblait réservée aux administrateurs.
La vraie question était :
Le chemin du terminal vers le backend applique-t-il réellement la même frontière d'autorisation lorsque la session websocket est créée ?
Dans ce cas, ce n'était pas le cas.
Les fonctionnalités de terminal sont parmi les surfaces les plus précieuses dans les logiciels d'infrastructure.
Pourquoi ?
Parce que toute inadéquation entre :
peut transformer un utilisateur normal d'application en un opérateur capable d'accéder à un shell.
C'est exactement pour cela que cette surface méritait d'être testée.
Je ne cherchais pas des plantages aléatoires ou des bugs de permission cosmétiques.
Je cherchais une classe de défaillance plus forte :
Est-ce qu'une fonctionnalité réservée aux administrateurs repose sur une vérification de confiance backend plus faible que ce que suggère l'interface utilisateur ?
C'était la bonne question.
Je n'ai pas abordé Coolify en fuzzant des points de terminaison aléatoires en espérant trouver quelque chose d'intéressant.
Le chemin le plus solide était d'identifier d'abord la frontière la plus risquée.
Pour Coolify, cette frontière était le flux de travail du terminal :
Cela faisait des routes d'amorçage le bon endroit où regarder.
Et c'est là que se trouvait le problème.
La cause racine était une inadéquation d'autorisation entre l'interface utilisateur du terminal et les routes d'amorçage du websocket du terminal.
À la révision vulnérable :
GET /terminal était protégé par can.access.terminalPOST /terminal/auth ne vérifiait que auth()->check()POST /terminal/auth/ips ne vérifiait que auth()->check()Cela signifie que l'interface utilisateur était contrôlée par l'autorisation du terminal, mais que la frontière de confiance du backend était contrôlée par la simple présence d'une session authentifiée.
Le service temps réel se fiait ensuite complètement à ces deux routes.
Dans docker/coolify-realtime/terminal-server.js :
verifyClient() envoyait une requête POST à /terminal/auth/terminal/auth/ipsC'est toute la chaîne du bug.
Parce qu'un membre d'équipe normal pouvait construire les entrées nécessaires à partir de la surface applicative habituelle :
/servers exposait les UUIDs de serveurs visibles/server/{uuid} exposait ip, user et port dans les champs de formulaire rendus/security/private-key exposait les UUIDs de clés privées d'équipe visibles/var/www/html/storage/app/ssh/keys/ssh_key@<uuid>
Le chemin d'exploitation était donc simple :
/terminal/auth/terminal/auth/ips/terminal/wsCe n'est pas une inadéquation théorique. C'est une défaillance pratique de l'autorisation backend.
La distinction importante est la confiance du backend et l'exécution de commandes.
Beaucoup de bugs ressemblent à :
Cela seul ne suffit pas.
La vraie question est :
L'utilisateur à faibles privilèges peut-il toujours satisfaire les vérifications de confiance backend qui comptent ?
Ici, la réponse était oui.
Ce n'était pas :
C'était :
C'est pourquoi c'était un véritable problème de sécurité.
J'ai validé cela dans un laboratoire local contrôlé construit à partir de :
06f60c9a98bead0c932c6adf7fd43a45d9149048
Le laboratoire utilisait :