
Nur für Administratoren vorgesehene Terminal-Bootstrap-Routen, die nur den Anmeldestatus prüfen, ermöglichen es einem normalen Teammitglied, das Echtzeit-Terminal-Backend von Coolify zu steuern und Befehle auf Teamservern auszuführen.
Administrator-exklusive Terminal-Bootstrap-Routen prüften nur den Anmeldestatus, sodass ein normales Teammitglied das Echtzeit-Terminal-Backend von Coolify ansteuern und Befehle auf Team-Servern ausführen konnte.
Ich fand dieses Problem bei der Überprüfung von Coolify, einer quelloffenen, selbst gehosteten PaaS, mit einer sehr direkten Sicherheitsfrage im Hinterkopf:
Wird der Terminalzugriff tatsächlich an der Backend-Vertrauensgrenze durchgesetzt oder nur in der UI?
In diesem Fall war die Antwort schlecht.
Coolify beabsichtigte, den Terminalzugriff auf Team-Administratoren und -Besitzer zu beschränken, aber die Echtzeit-Terminal-Bootstrap-Routen prüften nur, ob der Benutzer angemeldet war. Das erlaubte einem Teammitglied mit niedrigen Berechtigungen, die Websocket-Terminal-Vertrauensprüfungen zu erfüllen und die Befehlsausführung auf Team-Servern zu erreichen.
Ich habe dies Ende-zu-Ende in einem lokalen Labor validiert, das aus der anfälligen Revision erstellt wurde, und es später privat gemeldet. Das Problem wurde als CVE-2026-34048 zugewiesen mit:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Coolify: Coolify auf GitHub
CVE: CVE-2026-34048
Dies betraf Coolify, eine quelloffene, selbst gehostete PaaS. Auf seiner offiziellen Website gibt Coolify an, dass es 3.641+ Cloud-Kunden hat, und präsentiert sich als Plattform zum Bereitstellen von Websites, Datenbanken, Webanwendungen und 280+ One-Click-Diensten. Das offizielle v4.0-Changelog gibt außerdem an, dass Tausende von Unternehmen und Personen Coolify seit 1-2 Jahren in der Produktion nutzen.
photo0
Sitzung eines Teammitglieds mit niedrigen Berechtigungen -> /terminal/auth und /terminal/auth/ips prüfen nur den Anmeldestatus -> Echtzeit-Websocket vertraut diesen Antworten -> Mitglied enumeriert Team-Server und sichtbare SSH-Key-UUID -> /terminal/ws akzeptiert die Sitzung -> SSH-gestützter PTY wird erzeugt -> Shell-Zugriff auf Team-Server
Coolify ist eine selbst gehostete PaaS- und Bereitstellungsplattform.
Es verwaltet:
Die letzte Fähigkeit ist hier die Wichtige.
Sobald eine Plattform Terminals auf verwalteten Hosts öffnen kann, ist ihr Autorisierungsmodell nicht mehr nur Anwendungslogik. Es wird zu einer Infrastruktur-Vertrauensgrenze.
Die wichtige Frage war nicht, ob die Seite /terminal nur für Administratoren aussah.
Die eigentliche Frage war:
Erzwingt der Backend-Terminalpfad tatsächlich dieselbe Autorisierungsgrenze, wenn die Websocket-Sitzung erstellt wird?
In diesem Fall tat er es nicht.
Terminal-Funktionen gehören zu den wertvollsten Oberflächen in Infrastruktursoftware.
Warum?
Weil jede Diskrepanz zwischen:
einen normalen Anwendungsbenutzer in einen shell-fähigen Operator verwandeln kann.
Genau deshalb war diese Oberfläche einen Test wert.
Ich suchte nicht nach zufälligen Abstürzen oder kosmetischen Berechtigungsfehlern.
Ich suchte nach einer stärkeren Fehlerklasse:
Beruht eine Administrator-exklusive Funktion auf einer schwächeren Backend-Vertrauensprüfung als die UI vermuten lässt?
Das war die richtige Frage.
Ich bin nicht an Coolify herangegangen, indem ich zufällige Endpunkte gefuzzt und auf etwas Interessantes gehofft habe.
Der stärkere Weg war, zuerst die risikoreichste Grenze zu identifizieren.
Für Coolify war diese Grenze der Terminal-Workflow:
Das machte die Bootstrap-Routen zum richtigen Ort zum Suchen.
Und dort war das Problem.
Die Grundursache war eine Autorisierungsdiskrepanz zwischen der Terminal-UI und den Terminal-Websocket-Bootstrap-Routen.
In der anfälligen Revision:
GET /terminal war durch can.access.terminal geschütztPOST /terminal/auth prüfte nur auth()->check()POST /terminal/auth/ips prüfte nur auth()->check()Das bedeutet, dass die UI durch die Terminal-Autorisierung abgeschirmt war, aber die Backend-Vertrauensgrenze durch die bloße Anwesenheit einer authentifizierten Sitzung abgeschirmt wurde.
Der Echtzeitdienst vertraute dann diesen beiden Routen vollständig.
In docker/coolify-realtime/terminal-server.js:
verifyClient() POSTete an /terminal/auth/terminal/auth/ipsDas ist die gesamte Bug-Kette.
Weil ein normales Teammitglied die benötigten Eingaben aus der regulären Anwendungsoberfläche abgreifen konnte:
/servers zeigte sichtbare Server-UUIDs/server/{uuid} zeigte ip, user und port in gerenderten Formularfeldern/security/private-key zeigte sichtbare Team-Private-Key-UUIDs/var/www/html/storage/app/ssh/keys/ssh_key@<uuid>
Der Exploit-Pfad war also unkompliziert:
/terminal/auth aufrufen/terminal/auth/ips aufrufen/terminal/ws verbindenDas ist keine theoretische Diskrepanz. Das ist ein praktischer Backend-Autorisierungsfehler.
Der wichtige Unterschied ist Backend-Vertrauen und Befehlsausführung.
Viele Bugs sehen so aus:
Das allein reicht nicht.
Die eigentliche Frage ist:
Kann der Benutzer mit niedrigeren Berechtigungen trotzdem die relevanten Backend-Vertrauensprüfungen erfüllen?
Hier war die Antwort ja.
Dies war nicht:
Es war:
Deshalb war dies ein echtes Sicherheitsproblem.
Ich habe dies in einem kontrollierten lokalen Labor validiert, das aus folgendem Commit erstellt wurde:
06f60c9a98bead0c932c6adf7fd43a45d9149048
Das Labor verwendete:
http://127.0.0.1:18000[email protected]localhost -> coolify-testing-host:22 as rootsshws://127.0.0.1:6002/terminal/wsDas Mitgliedskonto war nicht dazu bestimmt, über die normale Admin-UI Terminalzugriff zu haben.
Das legte die erwartete Sicherheitsgrenze fest.
Mit der Mitgliedssitzung sandte ich:
POST /terminal/authPOST /terminal/auth/ipsBeide waren erfolgreich.
/terminal/auth/ips gab terminal-autorisierte Hosts zurück, darunter:
coolify-testing-host
host.docker.internal
localhost
127.0.0.1
Das bewies, dass die Backend-Bootstrap-Routen der Mitgliedssitzung vertrauten.
Von normalen authentifizierten Seiten aus konnte dasselbe Mitglied enumerieren:
Das reichte aus, um den Terminalpfad zu steuern, ohne dass geheimes Schlüsselmaterial preisgegeben werden musste.
Mit derselben authentifizierten Sitzung und dem XSRF-Token stellte ich eine Verbindung her zu:
ws://127.0.0.1:6002/terminal/ws
Das Payload verwendete dasselbe Befehlsformat, das das Terminal-Backend erwartet:
{"command":["timeout 30 ssh -i /var/www/html/storage/app/ssh/keys/ssh_key@ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PasswordAuthentication=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o RequestTTY=no -o LogLevel=ERROR -p '22' 'root'@'coolify-testing-host' 'bash -se' << \\P0C\nprintf '__COOLIFY_POC_BEGIN__\\n'; id; whoami; hostname; printf '__COOLIFY_POC_END__\\n'\nP0C"]}
Der Websocket gab zurück:
pty-ready
__COOLIFY_POC_BEGIN__
uid=0(root) gid=0(root) groups=0(root)
root
efa027413801
__COOLIFY_POC_END__
Das war der wichtige Beweis.
Nicht nur:
Sondern tatsächliche Befehlsausführung auf dem verwalteten Host über den Administrator-exklusiven Terminalpfad.
Ein Teil dieser Kette wäre bereits interessant gewesen.
Zum Beispiel:
/terminal/auth/terminal/auth/ipsAber das hätte immer noch Raum für Abweisung gelassen.
Die stärkere Validierung war Ende-zu-Ende:
Das schließt die Lücke zwischen "Autorisierungsfehler in der Theorie" und "praktischer Infrastruktur-Auswirkung in der Realität."
Es machte auch die Schwereeinstufung viel leichter zu verteidigen.
Dieses Problem wurde korrekt als Kritisch eingestuft.
Die Klassifizierung war:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Das ergibt Sinn.
Die Behauptung ist nicht, dass ein nicht authentifizierter Angreifer von Grund auf Shell-Zugriff erhalten kann.
Die Behauptung ist, dass:
Das ist eine erhebliche Änderung des Umfangs von einem Anwendungs-RBAC-Fehler hin zu einer Auswirkung auf den verwalteten Host.
Obwohl die Berechtigungen niedrig und nicht null sind, ist das Ergebnis dennoch eindeutig kritisch.
Manche Leute unterschätzen Schwachstellen, die mit PR:L beginnen.
Das ist ein Fehler, wenn die betroffene Funktion der Terminalzugriff ist.
Die eigentliche Frage ist nicht:
"War der Angreifer bereits eingeloggt?"
Die eigentliche Frage ist:
"Was kann dieser Benutzer mit niedrigeren Berechtigungen erreichen, sobald die Backend-Autorisierung falsch ist?"
In diesem Fall war die Antwort:
Das ist weit mehr als ein gewöhnlicher Mitglieder-Berechtigungsfehler.
Die minimal korrekte Behebung ist unkompliziert:
can.access.terminal sowohl auf POST /terminal/auth als auch auf POST /terminal/auth/ips anDas behebt den unmittelbaren Vertrauensgrenzen-Fehler.
In meinem lokalen Validierungs-Patch entfernte das Anwenden der Terminal-Autorisierungs-Middleware auf diese beiden Routen den Mitglied-zu-Terminal-Pfad.
Aber die stärkere Lektion ist, dass das Backend nicht den vom Angreifer kontrollierten SSH-Befehlsstrings als primäre Quelle für Zielmetadaten vertrauen sollte.
Empfohlene Härtung ist:
Das ist die Art von Korrektur, die man für eine Terminal-Funktion möchte:
Dieses Problem wurde privat über den Sicherheitsmeldefluss von GitHub gemeldet.
Der Bericht enthielt:
Das Problem wurde später zugewiesen:
CVE-2026-34048
Die wichtigste Lektion hier ist einfach:
Eine Administrator-exklusive UI ist bedeutungslos, wenn der Backend-Bootstrap-Kanal einem schwächeren Zustand vertraut.
Das ist die eigentliche Problemklasse.
All das ist irrelevant, wenn:
Sobald eine Plattform Infrastruktur verwaltet, hören Autorisierungsdiskrepanzen auf, gewöhnliche Zugriffskontrollfehler zu sein. Sie werden zu Schwachstellen mit Infrastrukturauswirkungen.
Das ist die eigentliche Erkenntnis.
Diese Schwachstelle drehte sich nicht um ein cleveres Payload.
Es ging darum, die richtige Vertrauensgrenze zu identifizieren.
Coolille beabsichtigte, den Terminalzugriff auf Administratoren zu beschränken. Aber das Echtzeit-Terminal-Backend vertraute Routen, die nur prüften, ob der Benutzer angemeldet war.
Von dort aus konnte ein Teammitglied mit niedrigen Berechtigungen den Websocket-Terminalpfad steuern und Shell-Ausführung auf einem Team-Server erreichen.
Deshalb wurde dies CVE-2026-34048.