
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 .
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/ipsAmbas tuvieron éxito.
/terminal/auth/ips devolvió hosts autorizados para terminal incluyendo:
coolify-testing-host
host.docker.internal
localhost
127.0.0.1
Eso demostró que las rutas de bootstrap del backend confiaban en la sesión del miembro.
Desde páginas autenticadas normales, el mismo miembro podía enumerar:
Eso fue suficiente para impulsar la ruta del terminal sin necesidad de divulgación de material secreto de clave.
Usando la misma sesión autenticada y el token XSRF, me conecté a:
ws://127.0.0.1:6002/terminal/ws
El payload usó el mismo formato de comando esperado por el backend del terminal:
{"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"]}
El websocket devolvió:
pty-ready
__COOLIFY_POC_BEGIN__
uid=0(root) gid=0(root) groups=0(root)
root
efa027413801
__COOLIFY_POC_END__
Esa era la prueba importante.
No solo:
Sino ejecución real de comandos en el host gestionado a través de la ruta del terminal solo para administradores.
Una parte de esta cadena ya habría sido interesante.
Por ejemplo:
/terminal/auth/terminal/auth/ipsPero eso aún dejaría margen para ser descartado.
La validación más sólida fue de extremo a extremo:
Eso cierra la brecha entre "error de autorización en teoría" e "impacto práctico en infraestructura en realidad."
También hizo que la severidad fuera mucho más fácil de defender.
Este problema fue clasificado correctamente como Crítico.
La clasificación fue:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Tiene sentido.
La afirmación no es que un atacante no autenticado pueda obtener acceso al shell de la nada.
La afirmación es que:
Eso es un cambio de alcance importante de una falla de RBAC de aplicación a un impacto en el host gestionado.
Por lo tanto, aunque los privilegios son bajos en lugar de ninguno, el resultado sigue siendo claramente crítico.
Algunas personas subestiman las vulnerabilidades que comienzan con PR:L.
Eso es un error cuando la funcionalidad afectada es el acceso al terminal.
La verdadera pregunta no es:
"¿El atacante ya había iniciado sesión?"
La verdadera pregunta es:
"¿Qué puede alcanzar ese usuario con menos privilegios una vez que la autorización del backend es incorrecta?"
En este caso, la respuesta fue:
Eso está mucho más allá de un error común de permisos de miembro.
La corrección mínima correcta es directa:
can.access.terminal tanto a POST /terminal/auth como a POST /terminal/auth/ipsEso aborda la falla inmediata del límite de confianza.
En mi parche de validación local, aplicar el middleware de autorización de terminal a esas dos rutas eliminó la ruta de miembro a terminal.
Pero la lección más importante es que el backend no debería confiar en cadenas de comando SSH controladas por el atacante como la fuente principal de metadatos del objetivo.
El endurecimiento recomendado es:
Ese es el tipo de remediación que se desea para una funcionalidad de terminal:
Este problema fue reportado de forma privada a través del flujo de reporte de seguridad de GitHub.
El reporte incluía:
El problema fue posteriormente asignado:
CVE-2026-34048
La lección principal aquí es simple:
la UI solo para administradores no importa si el canal de bootstrap del backend confía en un estado más débil.
Esa es la clase de problema real.
Nada de eso importa si:
Una vez que una plataforma gestiona infraestructura, los desajustes de autorización dejan de ser errores comunes de control de acceso. Se convierten en vulnerabilidades que impactan la infraestructura.
Esa es la conclusión real.
Esta vulnerabilidad no se trataba de un payload ingenioso.
Se trataba de identificar el límite de confianza correcto.
Coolify pretendía que el acceso al terminal fuera solo para administradores. Pero el backend del terminal en tiempo real confiaba en rutas que solo verificaban si el usuario había iniciado sesión.
A partir de ahí, un miembro del equipo con pocos privilegios podía manejar la ruta del terminal websocket y alcanzar la ejecución del shell en un servidor del equipo.
Es por eso que esto se convirtió en CVE-2026-34048.