
Rutas de arranque de terminal solo para administradores verificadas únicamente por estado de inicio de sesión, que permiten a un miembro normal del equipo manejar el backend de terminal en tiempo real de Coolify y ejecutar comandos en servidores del equipo.
Rutas de bootstrap de terminal solo para administradores verificaban únicamente el estado de inicio de sesión, lo que permitía a un miembro normal del equipo manejar el backend de terminal en tiempo real de Coolify y ejecutar comandos en los servidores del equipo.
Encontré este problema mientras revisaba Coolify, un PaaS autogestionado de código abierto, con una pregunta de seguridad muy directa en mente:
¿Se aplica realmente el acceso al terminal en el límite de confianza del backend, o solo en la interfaz de usuario?
En este caso, la respuesta fue mala.
Coolify pretendía que el acceso al terminal estuviera restringido a administradores y propietarios del equipo, pero las rutas de bootstrap del terminal en tiempo real solo verificaban si el usuario había iniciado sesión. Eso permitía que un miembro del equipo con pocos privilegios cumpliera con las comprobaciones de confianza del websocket del terminal y alcanzara la ejecución de comandos en los servidores del equipo.
Validé esto de extremo a extremo en un laboratorio local construido a partir de la revisión vulnerable y luego lo reporté de forma privada. El problema fue asignado como 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 en GitHub
CVE: CVE-2026-34048
Esto afectó a Coolify, un PaaS autogestionado de código abierto. En su sitio oficial, Coolify afirma que tiene 3,641+ clientes en la nube y se presenta como una plataforma para implementar sitios web, bases de datos, aplicaciones web y 280+ servicios con un solo clic. Su changelog oficial de v4.0 también indica que miles de empresas y personas han estado usando Coolify en producción durante 1-2 años.
photo0
sesión de miembro con pocos privilegios -> /terminal/auth y /terminal/auth/ips solo verifican estado de inicio de sesión -> websocket en tiempo real confía en esas respuestas -> miembro enumera servidor del equipo y UUID de clave SSH visible -> /terminal/ws acepta la sesión -> se genera PTY respaldado por SSH -> acceso shell en servidor del equipo
Coolify es un PaaS autogestionado y plataforma de despliegue.
Gestiona:
Esa última capacidad es la importante aquí.
Una vez que una plataforma puede abrir terminales a hosts gestionados, su modelo de autorización ya no es solo lógica de aplicación. Se convierte en un límite de confianza de infraestructura.
La pregunta importante no era si la página /terminal parecía solo para administradores.
La pregunta real era:
¿El camino del terminal del backend realmente aplica ese mismo límite de autorización cuando se crea la sesión websocket?
En este caso, no lo hizo.
Las funcionalidades de terminal son algunas de las superficies de mayor valor en el software de infraestructura.
¿Por qué?
Porque cualquier desajuste entre:
puede convertir a un usuario normal de la aplicación en un operador con capacidad de shell.
Eso es exactamente por qué valía la pena probar esta superficie.
No estaba buscando fallos aleatorios o errores de permisos cosméticos.
Buscaba una clase de fallo más fuerte:
¿Una funcionalidad solo para administradores depende de una verificación de confianza del backend más débil de lo que sugiere la UI?
Esa era la pregunta correcta.
No abordé Coolify fuzzeando endpoints aleatorios y esperando algo interesante.
El camino más sólido era identificar primero el límite de mayor riesgo.
Para Coolify, ese límite era el flujo de trabajo del terminal:
Eso hizo que las rutas de bootstrap fueran el lugar adecuado para buscar.
Y ahí es donde estaba el problema.
La causa raíz fue un desajuste de autorización entre la UI del terminal y las rutas de bootstrap del websocket del terminal.
En la revisión vulnerable:
GET /terminal estaba protegido por can.access.terminalPOST /terminal/auth solo verificaba auth()->check()POST /terminal/auth/ips solo verificaba auth()->check()Eso significa que la UI estaba controlada por autorización de terminal, pero el límite de confianza del backend estaba controlado por la simple presencia de sesión autenticada.
El servicio en tiempo real confiaba completamente en esas dos rutas.
En docker/coolify-realtime/terminal-server.js:
verifyClient() hacía POST a /terminal/auth/terminal/auth/ipsEsa es toda la cadena del error.
Porque un miembro normal del equipo podía construir las entradas necesarias desde la superficie normal de la aplicación:
/servers exponía UUIDs de servidores visibles/server/{uuid} exponía ip, user y port en campos de formulario renderizados/security/private-key exponía UUIDs de claves privadas del equipo visibles/var/www/html/storage/app/ssh/keys/ssh_key@<uuid>
Por lo tanto, la ruta de explotación era directa:
/terminal/auth/terminal/auth/ips/terminal/wsEso no es un desajuste teórico. Es una falla práctica de autorización del backend.
La distinción importante es la confianza del backend y la ejecución de comandos.
Muchos errores se ven como:
Eso solo no es suficiente.
La verdadera pregunta es:
¿Puede el usuario con menos privilegios seguir cumpliendo con las comprobaciones de confianza del backend que importan?
Aquí, la respuesta fue sí.
Esto no era:
Era:
Por eso era un problema de seguridad real.
Validé esto en un laboratorio local controlado construido a partir de:
06f60c9a98bead0c932c6adf7fd43a45d9149048
El laboratorio usaba:
http://127.0.0.1:18000[email protected]localhost -> coolify-testing-host:22 as rootsshws://127.0.0.1:6002/terminal/wsLa cuenta de miembro no estaba destinada a tener acceso al terminal a través de la UI normal orientada a administradores. Eso estableció el límite de seguridad esperado.
Usando la sesión del miembro, envié:
POST /terminal/authPOST /terminal/auth/ips