Cómo evitar un MITM en la primera conexión SSH en cualquier VPS o proveedor de nube
(joachimschipper.nl)- ssh-init-vm inyecta una clave privada temporal de host SSH con cloud-init para evitar ataques de intermediario en la primera conexión SSH a una VM nueva, y hace que solo se confíe en ella mientras se genera o importa la clave de host de largo plazo
- Funciona incluso en VPS o nubes sin funciones dedicadas de protección de acceso, como Hetzner Cloud, y solo requiere cloud-init, que tiene amplio soporte
- En el esquema habitual de Trust On First Use, si se responde
yesa la pregunta dessh“The authenticity of host [...] can't be established”, un atacante puede actuar como proxy del tráfico o presentar una máquina que parezca ser la VM del usuario - Si se coloca directamente la clave privada de host SSH de largo plazo en el userdata de cloud-init, eso ayuda a autenticar la primera conexión, pero el material de clave sensible puede quedar expuesto a través del servicio de metadatos, SSRF, los sistemas del proveedor de nube o la workstation del administrador
- ssh-init-vm guarda la clave temporal en un directorio temporal y no la agrega a
~/.ssh/known_hosts; además, no almacena la salida de la VM tal cual, sino que depende de la rotación de claves de host de OpenSSH para registrar la clave de largo plazo
Problema de exposición del userdata de cloud-init
- Si se inyecta una clave privada de host SSH de largo plazo con cloud-init, se puede poner la clave pública en
~/.ssh/known_hostspara autenticar la primera conexión, pero la clave privada puede filtrarse por varias vías - Un proceso arbitrario dentro de la VM normalmente puede obtener el userdata desde el servicio de metadatos, que suele ser legible; en una VM de Hetzner, el contenido de cloud-init puede verse en
http://169.254.169.254/hetzner/v1/userdata - Un atacante puede hacer que un proceso filtre metadatos mediante SSRF, y ese tipo de bloqueo no siempre se aplica incluso en entornos con funciones de protección dedicadas
- El userdata también puede quedar expuesto en otros sistemas del proveedor de nube; Hetzner incluso indica en la documentación de su API de creación de servidores que no se debe usar para guardar “passwords or other sensitive information”
- La workstation del administrador también puede ser un lugar donde el userdata de cloud-init quede almacenado o por donde pase, así que poner ahí una clave privada de largo plazo crea un riesgo de exposición mientras la clave siga siendo válida
Análisis de seguridad y modelo de amenazas
- La premisa es confiar en el protocolo OpenSSH y su implementación, sin depender de la capacidad del administrador para detectar un ataque
-
Protección frente a atacantes de red
- Lo que se protege es la integridad de la workstation del administrador y de la VM
- El atacante es un intermediario con control total de la red, y puede llegar a conocer el userdata de cloud-init en algún momento después de que el script termine con éxito o falle
- La protección funciona porque el atacante no conoce el material de claves cuando todavía tiene valor
- El script guarda la clave temporal de host SSH en un directorio temporal para evitar su uso accidental, y no agrega la clave temporal de host SSH a
~/.ssh/known_hosts
-
Si la workstation del administrador está comprometida
- Lo que se protege se limita a la VM y a su clave privada de host SSH de largo plazo
- Se asume que el atacante controla por completo la red y la workstation del administrador, pero no accede a la VM real
- La clave privada de host SSH de largo plazo nunca estuvo en la workstation del administrador, y como el atacante no accede a la VM real, no obtiene la clave de host de largo plazo de la VM
- Si el atacante sí accede a la VM real, es muy probable que pueda conocer las claves de host SSH con algo como
ssh root@<VM> cat /etc/ssh/ssh_host_*
-
Si la VM o el proveedor están comprometidos
- Lo que se protege se limita a la integridad de la workstation del administrador
- El atacante puede controlar por completo la red y también la VM o al proveedor
- Incluso en ese caso, la integridad de la workstation del administrador se protege gracias a la premisa de que OpenSSH es seguro
- Como defensa adicional, el script no escribe la salida de la VM directamente en
~/.ssh/known_hosts, sino que usa la rotación de claves de OpenSSH para registrar la clave de host SSH de largo plazo - Este enfoque evita que un host comprometido alimente con datos maliciosos al parser de
known_hosts, y hace que solo se escriban en~/.ssh/known_hostslas claves que la VM realmente controla - También permite manejar correctamente opciones de OpenSSH como
HashKnownHostsy otras opciones relacionadas en el futuro
Condiciones para que un ataque de intermediario funcione en la práctica
- Que un ataque MITM tenga éxito depende de si el usuario detecta en la práctica que todas las conexiones desde el principio van a la máquina equivocada, de si se niega a introducir una contraseña y de si configura reenvío del agent de
ssho de X11 - Bajo las condiciones simplificadas de ssh-mitm, el ataque tiene muchas probabilidades de funcionar si el atacante puede engañar al usuario dándole acceso a una máquina bajo control del atacante en lugar del host real
- También tiene éxito si el atacante engaña al usuario para obtener información con la que pueda iniciar sesión en el host real
- Si el usuario inicia sesión en la máquina del atacante con contraseña, el atacante puede tener éxito
- Si el usuario inicia sesión con cualquier método de autenticación y luego escribe una contraseña en el prompt, el atacante puede tener éxito
- Si el usuario inicia sesión con cualquier método de autenticación y reenvía la conexión de ssh-agent, el atacante puede tener éxito
- Si no se cumple ninguna de esas condiciones, el atacante necesita acceso al host real para engañar al usuario, pero es muy probable que fracase porque no puede iniciar sesión en el host real solo con la entrada del usuario
- Si el usuario reenvía una conexión X11, el atacante puede incluso lograr atacar la workstation del administrador independientemente del método de autenticación
1 comentarios
Comentarios de Lobste.rs
Está genial que se pueda automatizar. Yo venía verificando la huella SSH del servidor por un canal aparte desde la consola del proveedor de nube
No administro tantas instancias en la nube, así que no me molesta que haya algunos pasos manuales en el aprovisionamiento
Si ya tienes automatizada la zona DNS, también hay otro enfoque: crear un token de un solo uso con alcance muy limitado, por ejemplo permitiendo únicamente crear un registro en
my-server-hostname.example.netEse token se le pasa al servidor con
cloud-init, y el servidor sube su clave pública SSH como un registro SSHFP en el DNS. Después, el cliente SSH puede validar automáticamente el registro SSHFP, y la zona DNS debe estar firmada con DNSSECEste flujo permite que el servidor conserve su clave privada de host SSH y aun así evita la rotación de claves. La mayoría de los proveedores DNS no soportan tokens de acceso de un solo uso tan granulares, pero se podría tener un servicio web interno sencillo que valide el token y luego haga la llamada al API usando un token permanente y sin restricción de alcance. El servidor SSH no puede acceder a ese token permanente
Aun así, probablemente preferiría generar certificados SSH en vez de escribir en un dominio con DNSSEC, y a partir de ahí ya depende de qué solución se adapte mejor a cada entorno
Me da curiosidad saber si conoces algún software o proveedor que permita crear tokens tan flexibles, o si hace falta desarrollar una parte por cuenta propia
Bastante limpio. Aunque creo que en la fecha del artículo están invertidos el mes y el día
Hace tiempo hice un servicio experimental para explorar el mismo problema del huevo y la gallina con SSH
Crea registros DNS SSHFP bajo demanda; si te interesa, puedes ver https://github.com/tedb/sshfp
Me alegra que se mencione el servicio de metadatos 169.254.169.254, bastante consistente entre proveedores de VM. Se puede comprobar viendo varias entradas
cloudinit/source/DataSource*.pyen el código fuente decloud-initEn lo personal, cada vez me fatiga más
cloud-initpor su diseño y sus limitaciones. Me interesa unificar la configuración del sistema entre máquinas virtuales locales con QEMU, máquinas remotas, contenedores y hardware físicoEl proyecto arch-boxes muestra cómo se construye la imagen de cloud-init de ArchLinux, y es un conjunto de scripts de shell muy simple. Si este método se adapta con
guestfisho µvm, se pueden usar exactamente los mismos scripts para imágenes compatibles con OCI, imágenes de disco para QEMU o proveedores cloud, y para aprovisionar máquinas físicas nuevasCon algunas banderas de QEMU, se puede reproducir el mismo enfoque sin depender de
cloud-init. Hasta donde sé,systemd.system-credentialsno permite pasar claves de host temporales, solo credenciales para~/.ssh/authorized_keyscomossh.authorized_keys.rootEn su lugar, se puede crear un archivo unit que se ejecute en la etapa de initrd o junto con
systemd-firstboot.service. Ese unit puede incluirse previamente en la imagen o inyectarse temporalmente con credencialessystemd.extra-unit.*, y activarse con la opción de línea de comandos del kernelsystemd.wants=…. En QEMU, se puede simular la presencia de un servicio de metadatos con-netdev user,id=metadata,net=169.254.0.0/16,dhcpstart=169.254.0.15,guestfwd=tcp:169.254.169.254:80-cmd:…. Aun así, es muy probable que haya que activar la interfaz generada, y eso también podría resolverse mejor con un unit temporalAsí se obtiene bastante flexibilidad con una complejidad relativamente baja al aplicar una configuración del sistema consistente en varios tipos de “máquinas”. De hecho, si nos enfocamos solo en esta tarea, lo mejor parece ser que la herramienta de creación de imágenes genere imágenes de máquina con claves de host fijas y que instale como servicio de SystemD un script personalizado de rotación de claves de host, ejecutado en el primer reinicio o al apagar
HOOKdesystemden/etc/mkinitcpio.conf, puedes y en realidad debes escribir archivos unit de SystemD para las tareas que se ejecutan en initrdEn la práctica, usarlos solo es un poco más engorroso que escribir
{/etc,/usr/lib}/initcpio/hooksPero como es bastante fácil habilitar
systemd-networkingysystemd-resolveden initrd, se pueden programar tareas antes de que initrd le ceda el control al sistema de archivos raízClaro, en hardware físico como laptops puede no encajar tan bien, porque para la conexión Wi‑Fi hace falta algo como
NetworkManager, pero para VM de QEMU y VM alojadas funciona bien, y muchas tareas de arranque del sistema encajan naturalmente en ese espacioLa meta es no depender de
cloud-init, no quedar atado a un proveedor cloud específico, obtener consistencia entre máquinas físicas, contenedores, VM locales y VM alojadas, y reducir las dependencias prácticamente a SystemD