
Route di bootstrap del terminale solo per admin controllate solo per lo stato di login, che consentono a un normale membro del team di guidare il backend del terminale in tempo reale di Coolify ed eseguire comandi sui server del team.
Le route di bootstrap del terminale riservate agli amministratori controllavano solo lo stato di login, permettendo a un normale membro del team di utilizzare il backend del terminale in tempo reale di Coolify ed eseguire comandi sui server del team.
Ho trovato questo problema mentre esaminavo Coolify, un PaaS self-hosted open-source, con una domanda di sicurezza molto diretta in mente:
L'accesso al terminale è effettivamente imposto al confine di fiducia del backend, o solo nell'interfaccia utente?
In questo caso, la risposta è stata negativa.
Coolify intendeva limitare l'accesso al terminale agli amministratori e proprietari del team, ma le route di bootstrap del terminale in tempo reale controllavano solo se l'utente era loggato. Ciò permetteva a un membro del team con privilegi bassi di soddisfare i controlli di fiducia del websocket del terminale e raggiungere l'esecuzione di comandi sui server del team.
Ho validato questo end-to-end in un laboratorio locale costruito dalla revisione vulnerabile e successivamente l'ho segnalato privatamente. Il problema ha ricevuto l'assegnazione CVE-2026-34048 con:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Coolify: Coolify su GitHub
CVE: CVE-2026-34048
Questo ha interessato Coolify, un PaaS self-hosted open-source. Sul suo sito ufficiale, Coolify dichiara di avere 3.641+ clienti cloud, e si presenta come una piattaforma per distribuire siti web, database, applicazioni web e 280+ servizi con un clic. Il changelog ufficiale della v4.0 afferma inoltre che migliaia di aziende e persone hanno utilizzato Coolify in produzione per 1-2 anni.
photo0
sessione membro del team con privilegi bassi -> /terminal/auth e /terminal/auth/ips controllano solo lo stato di login -> websocket in tempo reale si fida di queste risposte -> il membro enumera il server del team e l'UUID visibile della chiave SSH -> /terminal/ws accetta la sessione -> viene generato un PTY basato su SSH -> accesso shell sul server del team
Coolify è un PaaS self-hosted e una piattaforma di deployment.
Gestisce:
L'ultima capacità è quella importante qui.
Una volta che una piattaforma può aprire terminali su host gestiti, il suo modello di autorizzazione non è più solo logica applicativa. Diventa un confine di fiducia dell'infrastruttura.
La domanda importante non era se la pagina /terminal sembrasse riservata agli amministratori.
La vera domanda era:
Il percorso del terminale nel backend impone effettivamente lo stesso confine di autorizzazione quando viene creata la sessione websocket?
In questo caso, non lo faceva.
Le funzionalità del terminale sono tra le superfici di maggior valore nel software per infrastrutture.
Perché?
Perché qualsiasi disallineamento tra:
può trasformare un normale utente dell'applicazione in un operatore con accesso shell.
È esattamente per questo che valeva la pena testare questa superficie.
Non cercavo crash casuali o bug di permessi estetici.
Cercavo una classe di fallimento più forte:
Una funzionalità riservata agli amministratori si basa su un controllo di fiducia del backend più debole di quanto suggerisca l'UI?
Quella era la domanda giusta.
Non ho affrontato Coolify fuzzando endpoint casuali sperando in qualcosa di interessante.
Il percorso più forte era identificare prima il confine a più alto rischio.
Per Coolify, quel confine era il flusso di lavoro del terminale:
Questo rendeva le route di bootstrap il posto giusto dove guardare.
Ed è lì che si trovava il problema.
La causa principale era un disallineamento di autorizzazione tra l'UI del terminale e le route di bootstrap del websocket del terminale.
Alla revisione vulnerabile:
GET /terminal era protetto da can.access.terminalPOST /terminal/auth controllava solo auth()->check()POST /terminal/auth/ips controllava solo auth()->check()Ciò significa che l'UI era bloccata dall'autorizzazione del terminale, ma il confine di fiducia del backend era bloccato dalla semplice presenza di una sessione autenticata.
Il servizio in tempo reale si fidava poi completamente di queste due route.
In docker/coolify-realtime/terminal-server.js:
verifyClient() inviava POST a /terminal/auth/terminal/auth/ipsQuesta è l'intera catena del bug.
Perché un normale membro del team poteva costruire gli input necessari dalla superficie applicativa regolare:
/servers esponeva UUID di server visibili/server/{uuid} esponeva ip, user e port in campi form renderizzati/security/private-key esponeva UUID di chiavi private del team visibili/var/www/html/storage/app/ssh/keys/ssh_key@<uuid>
Quindi il percorso di sfruttamento era diretto:
/terminal/auth/terminal/auth/ips/terminal/wsNon è un disallineamento teorico. È un fallimento pratico dell'autorizzazione del backend.
La distinzione importante è la fiducia del backend e l'esecuzione dei comandi.
Molti bug appaiono come:
Da solo non basta.
La vera domanda è:
L'utente con privilegi inferiori può comunque soddisfare i controlli di fiducia del backend che contano?
In questo caso, la risposta era sì.
Questo non era:
Era:
Ecco perché era un vero problema di sicurezza.
Ho validato questo in un laboratorio locale controllato costruito da:
06f60c9a98bead0c932c6adf7fd43a45d9149048
Il laboratorio utilizzava:
http://127.0.0.1:18000[email protected]localhost -> coolify-testing-host:22 as rootsshws://127.0.0.1:6002/terminal/wsL'account membro non doveva avere accesso al terminale tramite la normale UI per amministratori.
Questo stabiliva il confine di sicurezza previsto.
Usando la sessione del membro, ho inviato:
POST /terminal/authPOST /terminal/auth/ipsEntrambe hanno avuto successo.
/terminal/auth/ips ha restituito host autorizzati al terminale, inclusi: