
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:
coolify-testing-host
host.docker.internal
localhost
127.0.0.1
Ciò ha dimostrato che le route di bootstrap del backend si fidavano della sessione del membro.
Dalle pagine autenticate normali, lo stesso membro poteva enumerare:
Questo era sufficiente per guidare il percorso del terminale senza bisogno di divulgazione di materiale segreto delle chiavi.
Usando la stessa sessione autenticata e il token XSRF, mi sono connesso a:
ws://127.0.0.1:6002/terminal/ws
Il payload utilizzava lo stesso formato di comando previsto dal backend del terminale:
{"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"]}
Il websocket ha restituito:
pty-ready
__COOLIFY_POC_BEGIN__
uid=0(root) gid=0(root) groups=0(root)
root
efa027413801
__COOLIFY_POC_END__
Questa era la prova importante.
Non solo:
Ma esecuzione effettiva di comandi sull'host gestito tramite il percorso del terminale riservato agli amministratori.
Una parte di questa catena sarebbe già stata interessante.
Ad esempio:
/terminal/auth/terminal/auth/ipsMa ciò avrebbe ancora lasciato spazio a facili smentite.
La validazione più forte era end-to-end:
Questo chiude il divario tra "bug di autorizzazione in teoria" e "impatto pratico sull'infrastruttura in realtà."
Ha anche reso molto più facile difendere la severità.
Questo problema è stato correttamente classificato come Critico.
La classificazione era:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Ha senso.
L'affermazione non è che un attaccante non autenticato possa ottenere accesso shell dal nulla.
L'affermazione è che:
Questo è un cambiamento di ambito importante da fallimento RBAC applicativo a impatto sull'host gestito.
Quindi, anche se i privilegi sono bassi anziché nulli, il risultato rimane chiaramente critico.
Alcune persone sottovalutano le vulnerabilità che iniziano con PR:L.
Questo è un errore quando la funzionalità interessata è l'accesso al terminale.
La vera domanda non è:
"L'attaccante era già loggato?"
La vera domanda è:
"Cosa può raggiungere quell'utente con privilegi inferiori una volta che l'autorizzazione del backend è sbagliata?"
In questo caso, la risposta era:
Questo va ben oltre un normale bug di permessi di un membro.
La correzione minima corretta è semplice:
can.access.terminal sia a POST /terminal/auth che a POST /terminal/auth/ipsQuesto risolve il fallimento immediato del confine di fiducia.
Nella mia patch di validazione locale, applicare il middleware di autorizzazione del terminale a quelle due route ha rimosso il percorso membro-al-terminale.
Ma la lezione più forte è che il backend non dovrebbe fidarsi delle stringhe di comando SSH controllate dall'attaccante come fonte primaria di metadati di destinazione.
L'hardening raccomandato è:
Questo è il tipo di remediation che si desidera per una funzionalità del terminale:
Questo problema è stato segnalato privatamente tramite il flusso di segnalazione di sicurezza di GitHub.
Il report includeva:
Il problema è stato poi assegnato:
CVE-2026-34048
La lezione principale qui è semplice:
un'UI riservata agli amministratori non conta se il canale di bootstrap del backend si fida di uno stato più debole.
Questa è la vera classe di problema.
Niente di tutto ciò conta se:
Una volta che una piattaforma gestisce l'infrastruttura, i disallineamenti di autorizzazione smettono di essere normali errori di controllo degli accessi. Diventano vulnerabilità con impatto sull'infrastruttura.
Questo è il vero insegnamento.
Questa vulnerabilità non riguardava un payload intelligente.
Riguardava l'identificazione del confine di fiducia giusto.
Coolify intendeva l'accesso al terminale come riservato agli amministratori. Ma il backend del terminale in tempo reale si fidava di route che controllavano solo se l'utente era loggato.
Da lì, un membro del team con privilegi bassi poteva guidare il percorso websocket del terminale e raggiungere l'esecuzione shell su un server del team.
Ecco perché questo è diventato CVE-2026-34048.