4 puntos por GN⁺ 2024-06-24 | 1 comentarios | Compartir por WhatsApp
  • sudo y doas dependen de binarios setuid y de la elevación de privilegios, por lo que esto es un experimento que delega la ejecución de comandos como root a un sshd local enlazado a un socket de dominio Unix
  • El objetivo es permitir que solo los usuarios autorizados ejecuten comandos como root sin dejar la capacidad de elevar privilegios disponible para toda la sesión del usuario
  • La implementación se compone de un archivo de clave SSH exclusivo para root, /run/sshd/sshd.sock con acceso restringido y una instancia separada de sshd con las opciones AuthorizedKeysFile y PermitRootLogin=yes
  • Como ssh no tiene una opción para pasarle directamente un socket ya existente, al principio se usaron ProxyCommand y socat, y después ProxyUseFdpass junto con un pequeño script en Python para pasar el descriptor de archivo del socket
  • Este enfoque funciona y el manejo de seguridad depende principalmente de OpenSSH, pero para el uso diario conviene convertir passfd.py en un pequeño ejecutable y envolver todo el comando ssh con un wrapper

Las limitaciones estructurales de sudo y doas

  • sudo y doas dependen de binarios setuid y de la elevación de privilegios para ejecutar comandos como root
  • Este diseño tiene varias limitaciones
    • Toda la sesión del usuario debe conservar la capacidad de realizar elevación de privilegios
    • No funciona cuando toda la sesión del usuario se ejecuta dentro de un espacio de nombres de usuario restringido
    • Los binarios setuid imponen restricciones sobre la configuración de seguridad de todo el sistema
  • s6-sudod es una alternativa que divide el programa entre un servidor con privilegios y un cliente sin privilegios

Objetivo del experimento

  • Usar ssh localmente para cumplir el mismo papel que sudo, pero sin exponer esa instancia de sshd a la red
  • Hay dos condiciones clave
    • Solo los usuarios autorizados pueden ejecutar comandos como root
    • No se usa elevación de privilegios

Configuración de sshd para inicio de sesión local como root

  • Se crea una clave SSH dedicada para usarla solo en la autenticación de root y se guarda en /root/.ssh/local_keys en lugar de authorized_keys normal
  • Se enlaza una instancia separada de sshd a un socket de dominio Unix y se restringen los permisos de /run/sshd/ para que los usuarios no autorizados no puedan acceder al socket
  • El ejemplo de ejecución usa s6-ipcserver para crear /run/sshd/sshd.sock y pasar estas opciones a sshd
    • AuthorizedKeysFile=/root/.ssh/local_keys
    • PermitRootLogin=yes
  • No se modifica /etc/ssh/sshd_config
    • Porque no se quiere permitir el inicio de sesión de root en el sshd existente enlazado a la red
    • En la configuración existente se mantiene PermitRootLogin no

Bloqueo de la cuenta root y manejo del inicio de sesión por contraseña

  • La cuenta root estaba bloqueada para impedir cualquier tipo de inicio de sesión, usando para ello un prefijo ! delante del hash de la contraseña
  • sshd interpreta ese prefijo ! como bloqueo de cuenta y por eso no permite el inicio de sesión de root
  • Se cambia el valor de la contraseña de root en /etc/passwd para reemplazar ! por *
    • sshd no interpreta * como un valor especial de bloqueo
    • Como * no coincide con ningún hash de contraseña, el inicio de sesión por contraseña sigue desactivado en la práctica
  • Además, en sshd_config se establece PasswordAuthentication no, y es más seguro desactivar la autenticación basada en contraseña en sshd

Conectarse a un socket Unix con ssh

  • sshd tiene una opción -i para pasarle un socket existente, pero ssh no tiene una opción equivalente
  • Al principio se usó socat con ProxyCommand para conectarse a /run/sshd/sshd.sock
  • El comando de conexión se compone de estos elementos
    • ProxyCommand='socat STDIO UNIX-CONNECT:/run/sshd/sshd.sock'
    • La clave root dedicada se especifica con -i .ssh/root-key.pub
    • Se conecta a root@root
    • Luego cambia al directorio actual y ejecuta un shell de inicio de sesión
  • La clave SSH usada era una clave ligada a hardware, así que había que tocar físicamente el dispositivo para aprobar la conexión
  • También podría servir como alternativa un ssh-agent que solo publique la clave después de una aprobación explícita, como hissh-agent

La sobrecarga de socat y ProxyUseFdpass

  • socat lee toda la entrada de ssh y luego la escribe en el socket, así que la sobrecarga de conexión en la práctica se duplica
  • ProxyUseFdpass hace que el comando envíe el descriptor de archivo del socket a ssh a través de stdout, y que ssh use ese socket para conectarse
  • A partir de un ejemplo de uso de OpenSSH ProxyUseFdpass de 2016, se escribió el script en Python passfd.py
    • Se conecta al socket de dominio Unix /run/sshd/sshd.sock
    • Pasa el descriptor de archivo con sendmsg y SCM_RIGHTS
  • Después, el comando de conexión pasa a usar passfd.py en ProxyCommand y agrega ProxyUseFdpass=yes
  • nc -FU /run/sshd/sshd.sock también parece posible, pero el manual indica explícitamente que -F no puede usarse junto con -U

Conclusión y forma de uso real

  • Esta técnica funciona y delega principalmente en OpenSSH el manejo sensible de la seguridad
  • OpenSSH tiene un buen historial y permite usar varios métodos de autenticación, incluidas las claves SSH basadas en hardware
  • El proceso de configuración en un host nuevo no tiene pasos complejos, y el comando ipcserver puede ejecutarse mediante el gestor de servicios del sistema
  • passfd.py se parece más a un hack rápido para llevar a cabo el experimento
  • Para el uso diario, es más adecuado crear un pequeño ejecutable que cumpla el mismo papel y dejarlo en /usr/local/bin, además de envolver también todo el comando ssh con un pequeño wrapper

1 comentarios

 
GN⁺ 2024-06-24
Opiniones de Hacker News
  • La principal razón para oponerse a este enfoque es el aumento de complejidad. En lugar de un único binario suid que lee un archivo de configuración y llama a exec(), ahora hay un binario que se ejecuta como root y escucha en un socket UNIX, y otro binario que habla con ese socket, y ambos además tienen que manejar criptografía asimétrica.
    Si el argumento central contra sudo/doas es “hay un binario suid accesible para todos los usuarios y, si tiene un bug, puede explotarse para elevar privilegios”, entonces se puede hacer algo como esto:
    chgrp wheel /usr/bin/sudo
    chmod o-rwx /usr/bin/sudo
    Si pones a los usuarios de sudo en el grupo wheel, solo los usuarios que pueden usar sudo podrán leer los bytes del archivo desde el disco y ejecutarlo. En términos de control de acceso, la seguridad es casi la misma que con el enfoque de sshd, donde solo los usuarios de wheel pueden acceder al socket UNIX, pero la complejidad es mucho menor.
    Además, el enfoque con sshd no puede limitar el acceso root a comandos específicos como sudo, así que aunque hubiera un bug que permitiera eludir las restricciones de comandos de sudo, no otorgaría más privilegios que el enfoque con sshd.
    Si te preocupa que el gestor de paquetes arruine los permisos de /usr/bin/sudo, puedes hacer que cron los corrija periódicamente, o eliminar sudo por completo e instalarlo manualmente desde el código fuente en otra ubicación. Claro que entonces también tendrás que encargarte tú del mantenimiento y las actualizaciones.

    • Personalmente uso etckeeper para rastrear todos los cambios en /etc. Ya sea una instalación/actualización de software o un cambio hecho por una persona, queda registrado; y al actualizar a una nueva versión también resulta útil porque puedes crear un parche con los cambios locales, aplicarlo sobre una instalación limpia y luego revisar los conflictos con una fusión de 3 vías.
      https://etckeeper.branchable.com/
    • Poner a los usuarios de sudo en el grupo wheel para que solo quienes puedan usar sudo puedan leer y ejecutar el archivo suena bastante razonable. Me pregunto por qué no es la configuración predeterminada en todas partes.
    • Disculpen mi ignorancia. Me gustaría que me explicaran qué es el grupo wheel y qué hace. Sé que esto podría abrir un debate complejo.
    • ¿No sería posible hacer que /usr/bin/sudo sea immutable? Parece que eso ayudaría a impedir que el gestor de paquetes lo toque.
    • Para la parte de no poder limitar el acceso root a comandos específicos, existe la infraestructura de ForcedCommand.
  • ¿No es esto lo que hace ahora systemd run0? systemd tiene una nueva herramienta llamada run0, aunque en realidad no es una herramienta completamente nueva: si invocas el viejo systemd-run mediante un enlace simbólico llamado run0, se comporta como un reemplazo de sudo.
    La diferencia clave es que en realidad no es SUID. Le pide al administrador de servicios que ejecute un comando o shell con el UID del usuario de destino, asigna una nueva PTY y luego pasa datos entre la TTY original y esta PTY.
    En otras palabras, el comando de destino no hereda el contexto del cliente, sino que se ejecuta en un contexto de ejecución aislado recién bifurcado desde PID 1. Se pasa $TERM, pero eso es una excepción explícita, es decir, algo más parecido a una lista de permitidos que a una lista de bloqueados.
    En muchos sentidos, run0 se puede considerar más cercano al comportamiento de ssh que al de sudo.
    https://mastodon.social/@pid_eins/112353324518585654

    • ¿Por qué systemd tiene que reinventarlo todo?
  • ¿Me estoy perdiendo de algo? No veo cómo iniciar sesión por ssh como root sea más seguro que usar sudo. Siempre me enseñaron a no permitirlo, así que tampoco sé qué tan peligroso es en la práctica.
    Aquí parece que hubo consideraciones para bloquear usuarios remotos, así que no me refiero a ese aspecto de seguridad.
    Tal vez tenga que ver con la limitación 3 de sudo, pero al menos frente a la 1 no veo ninguna ventaja.
    Entiendo que es un experimento, pero me parece que sería más vulnerable que sudo, no menos. Un proxy de socket abierto parece vulnerable a ataques de intermediario.
    Aun así, aprendí algunas técnicas que se pueden hacer con herramientas antiguas, y estuvo bien que mostrara algo nuevo.

    • El binario sudo es suid root y, al ser un binario con privilegios, queda expuesto directamente a usuarios no confiables. Si algo sale mal dentro de sudo, todo el entorno del usuario se convierte en superficie de ataque y puede explotarse.
      El enfoque con ssh no expone un binario suid y usa la capa de red de ssh. Por lo tanto, no es menos seguro que acceder a ssh por la red, lo cual se considera bastante seguro.
    • Una de las grandes ventajas de sudo es que, en lugar de simplemente lanzar algo como sudo bash, puedes ejecutar comandos individuales con sudo y así mejorar la auditabilidad.
    • Iniciar sesión por ssh como root, en el artículo, se refiere a un servidor SSH adicional que escucha en un socket Unix. El modelo de amenazas típico de exponer el inicio de sesión root a Internet puede no aplicar aquí.
    • Este enfoque compara errores de configuración teóricos o vulnerabilidades que podrían existir o no, contra una nueva superficie de ataque creada al ejecutar un nuevo daemon.
      Ese daemon también puede tener errores de configuración o vulnerabilidades, y reduce varias capas de autorización basadas en usuarios a un único nivel root.
      Aun así, parece que se lo considera más seguro. Desde una perspectiva de seguridad razonable, no puede considerarse más seguro; simplemente es un enfoque distinto.
    • Soy escéptico sobre el enfoque del artículo enlazado, pero en la frase “el inicio de sesión SSH remoto directo como root es peligroso” hay algo de haber caído en el miedo, la incertidumbre y la duda.
      En realidad, iniciar sesión por SSH directamente como root es estrictamente más seguro que iniciar sesión como usuario por SSH remoto y luego usar sudo.
      Si user@home hace ssh a root@server, root@server queda comprometido solo si user@home es comprometido.
      En cambio, si user@home hace ssh a user@server y luego se convierte en root@server con sudo, basta con que se comprometa user@home o user@server para que root quede comprometido. En particular, en user@server suelen ejecutarse otros programas, como daemons o tareas cron.
      No hay que regalarle a alguien que logró infectar por esa vía una elevación gratuita a root, y además movimiento lateral, algo frecuente por la reutilización de contraseñas.
      Por supuesto, esto no aplica si sudo se usa en modo exclusivo de lista de comandos permitidos y no recibe contraseñas ni credenciales totalmente accesibles desde el host remoto.
  • ¿Qué pasa si ssh no se inicia al arrancar? Recuerdo que, en una configuración común, se inicia dependiendo de la red. Entonces tampoco se podría iniciar sesión desde la consola failsafe
    No sé qué se obtiene realmente en comparación con sudo o su. En vez de evitar un binario setuid, se termina ejecutando un servicio de red como root, aunque solo se conecte a un socket

    • Hablando como alguien que usa una configuración parecida: lo hago así en mi desktop principal. Si llega lo peor de lo peor, tengo backups de todo, así que reinstalo el sistema
      ¿Qué pasa si muere el SSD? Tampoco en ese caso podría iniciar sesión en la consola failsafe
      En 30 años usando Linux, me han fallado discos duros muchísimas más veces que veces en que el daemon sshd no arrancó; en proporción, es como dividir entre cero
      Si el daemon sshd del sistema operativo no arranca al azar, lo tomaría como señal de que es hora de pasarme a un sistema operativo más estable
      Lo que se gana frente a sudo o su es que los exploits de escalamiento de privilegios locales se vuelven mucho más difíciles
    • La consola de Linux, es decir, la tty que aparece en una pantalla local o en un KVM remoto, y los dispositivos ttyS* de puertos seriales e IPMI SoL, no usan sudo ni su
      Esas consolas usan programas como getty o un gestor de ventanas, que son programas no suid iniciados como root
      Lo correcto es dejar configurada una contraseña de root para iniciar sesión en la consola
    • En mi caso uso setuid/sudo por los logs de auditoría. Hoy casi no administro cajas multiusuario o multiservicio, y lo que es multitenant en su mayoría es k8s, así que se puede usar el endpoint de kubectl en vez de ssh
      Si alguien puede iniciar sesión, dejo que pueda hacer setuid a root. Si es una caja de k8s, eso corresponde al equipo de infraestructura de plataforma, y el acceso a los servicios encima de eso pasa por el proveedor de permisos de k8s
      Desde el punto de vista del equipo de infraestructura de plataforma, si solo necesitan métricas y logs, ya están fuera de la caja; y si necesitan disparar algún trabajo o workflow, pueden usar un pipeline
      Aun así, si alguien inicia sesión y hace tareas como root, quiero que quede un log de auditoría
      No se me ocurre ninguna caja mía en la que alguien con permiso de login no tenga privilegios completos de root
      Por supuesto entiendo los casos en que un servicio hace setuid, pero en servicios normalmente systemd hace setuid para bajar privilegios, no para elevarlos
    • Si tienes acceso al bootloader, todavía puedes configurar systems.unit=emergency.target, init=/bin/bash, rd.break=pre-pivot o arrancar en un entorno live CD. Todas las opciones comunes de recuperación de emergencia siguen funcionando
      Y en una emergencia menos grave, tampoco veo por qué esta instancia de sshd tendría que estar ligada a la red
  • Es una pena que no incluya todas las desventajas de este enfoque. sudo permite controlar qué grupos pueden ejecutar qué comandos, qué argumentos pueden recibir esos comandos, si se permite crear subshells, etc.
    Con este método se pierde mucho de ese control granular, y se depende de claves de confianza que son más difíciles de administrar que editar el archivo sudoers
    Para ver las cosas sorprendentes que puede hacer sudo, de verdad recomiendo el libro Sudo Mastery

    • SSH también puede hacer parte de eso con ForceCommand, pero coincido en que no es tan flexible ni preciso
  • Esto es una idea similar a run0 de systemd: https://news.itsfoss.com/systemd-run0/

    • Y run0 no es una solución temporal hecha en casa. Fue auditada y probablemente sea mejor que algo casero
  • Uno de los problemas de ssh es que la creación de procesos no forma parte del protocolo. Y, al ser un protocolo remoto, no puede pasar recursos locales al proceso hijo
    Por eso no se puede pasar un arreglo de argumentos separado por null, ni descriptores de archivo adicionales, ni especificar el ejecutable
    En su lugar, se pasa una sola cadena al shell configurado en el servidor. Entonces se necesita escape de shell, y además hay que saber qué shell corre del lado del servidor
    Para usar SSH como un reemplazo adecuado de sudo, haría falta una extensión con algo cercano a posix_spawn

  • Totalmente de acuerdo. Yo hago algo parecido y también lo expliqué alguna vez en un comentario de HN
    Mi método es un poco distinto. Uso una máquina dedicada como consola SSH física, y esa máquina está en una LAN privada separada de las demás máquinas de mi casa. No es un switch administrable, sino un switch común; uso cables Ethernet y no hay trunks
    Solo se puede iniciar sesión por SSH, y le conecté una Yubikey
    La PC de escritorio tiene su propio firewall y solo permite tráfico SSH proveniente de la dirección IP/MAC de esa consola SSH. Eso solo en la LAN privada que comparten; en otra LAN física, la desktop puede acceder a Internet
    El daemon sshd solo permite login con clave pública/privada y tiene deshabilitado el login con contraseña
    Cuando necesito root, arranco la “consola SSH”. Como es una máquina con casi nada, enciende muy rápido. Inicio sesión, recupero con la flecha arriba la línea ssh root@..., presiono Enter y toco la Yubikey
    Esa consola SSH y el teclado están sobre el escritorio, siempre al alcance de la mano
    Que iptables/nftables + sshd en una LAN privada, además físicamente separada de otra LAN privada, sea más o menos seguro que el binario sudo o su, que cada quien lo juzgue
    Si me preguntan “por qué”, respondería “porque puedo”. Lo configuré hace tanto que ni recuerdo cuándo fue. Probablemente empecé a jugar con esta idea hace unos 2 años, y la he usado desde entonces. No he tenido ningún problema

    • Aunque la configuración que tienes no sea exactamente igual, parece similar al concepto de host bastión
  • Es una solución elegante para este problema. No hace falta tratar a los usuarios como niños, pero al mismo tiempo hay que evitar posibles errores con valores predeterminados razonables
    Si necesitas root, puedes iniciar sesión como root desde la consola, así que creo que ni siquiera hace falta su. Este método se acerca lo máximo posible a iniciar sesión como root en una tty de consola

    • La idea de iniciar sesión como root desde la consola cuando se necesita root tiene dos problemas
      Primero, a diferencia de sudo, todos los usuarios tienen que conocer la contraseña de root
      Segundo, si todos simplemente inician sesión como root, no hay forma de auditar quién inició sesión realmente ni qué hizo
  • Hace 10 años probé algo parecido. No tenía la parte de sockets UNIX; levanté un sshd aparte que escuchaba solo en localhost, y tampoco tuve que lidiar con SCM_RIGHTS
    No hubo resultados particularmente buenos ni malos; simplemente perdí el interés y no trasladé la configuración a la siguiente máquina