6 puntos por GN⁺ 2023-08-24 | 1 comentarios | Compartir por WhatsApp
  • SSH es una herramienta que va más allá del acceso remoto: también abarca port forwarding, jump hosts, archivos de configuración y gestión de llaves; los ejemplos con servidores web también se pueden aplicar tal cual a otros servicios como RDP y SQL
  • El port forwarding local, remoto y dinámico se configura con -L, -R y -D, respectivamente; la diferencia clave es de qué lado se abre el puerto y hacia dónde fluye el tráfico
  • En redes donde el acceso directo está bloqueado, se pueden encadenar varios saltos SSH con jump hosts -J y ProxyJump para llegar al destino
  • ssh-agent y el agent forwarding -A permiten usar llaves locales incluso en hosts remotos, lo que resulta cómodo, pero antes hay que revisar los riesgos de seguridad por posible abuso del agente
  • Usar ~/.ssh/config, ssh-copy-id, ssh-keygen y la consola SSH ~?·~C en conjunto reduce la repetición de opciones y facilita agregar forwarding durante una sesión, distribuir llaves públicas y generar o inspeccionar llaves

Supuestos para entender el port forwarding de SSH

  • El port forwarding de SSH es difícil de entender solo con diagramas; es más fácil capturar el flujo cuando se ven comandos reales junto con escenarios de red
  • Los ejemplos se basan en el acceso a un servidor web, pero el mismo enfoque aplica a casi cualquier servicio, como RDP, SQL, etc.
  • Las opciones que se repiten tienen el siguiente significado
    • -N: no ejecuta comandos en el servidor remoto ni abre una shell
    • -f: envía SSH al segundo plano
    • root@host: inicia sesión con el usuario y host que crearán el túnel

Port forwarding local -L

  • El port forwarding local reenvía un puerto de la máquina local a un puerto del servidor remoto
  • Escenario de ejemplo
    • internal-web.int aloja una página web accesible solo desde la interfaz loopback
    • Desde campfire.int se puede acceder por SSH a internal-web.int
    • Se quiere acceder al servidor web de internal-web.int a través de un puerto local de campfire.int
  • Comando a usar
    • ssh -N -f -L 1337:127.0.0.1:80 root@internal-web.int
  • Interpretación de las opciones
    • -L: especifica forwarding local
    • 1337:127.0.0.1:80: enlaza el puerto local 1337 con 127.0.0.1:80 del remoto
  • Después de crear el túnel, desde campfire.int se pueden enviar solicitudes al puerto local 1337 e interactuar con el puerto 80 de internal-web.int
  • Lo que hay que recordar es que con -L, el puerto local va a la izquierda de la dirección

Port forwarding remoto -R

  • El port forwarding remoto expone un puerto accesible localmente como un puerto del servidor remoto
  • Escenario de ejemplo
    • internal-web.int aloja una página web accesible solo desde loopback
    • campfire.int no puede acceder directamente a internal-web.int por el firewall
    • vuln-server.int es accesible tanto desde campfire.int como desde internal-web.int
  • Comando a usar
    • ssh -N -f -R 3000:127.0.0.1:80 root@vuln-server.int
  • Interpretación de las opciones
    • -R: especifica forwarding remoto
    • 3000:127.0.0.1:80: enlaza el puerto 3000 de vuln-server.int con 127.0.0.1:80 del local
  • Luego, si se envía una solicitud con curl a vuln-server.int:3000, se puede acceder a la página web interna que corre en el puerto 80 de internal-web.int
  • Lo que hay que recordar es que con -R, el puerto local va a la derecha de la dirección

Port forwarding dinámico -D y proxy SOCKS

  • El port forwarding dinámico crea un proxy SOCKS con la opción -D y envía el tráfico a través del salto SSH
  • Escenario de ejemplo
    • internal-web.int aloja una aplicación web accesible solo desde la red interna
    • vuln-server.int está en la misma red interna y puede acceder a internal-web.int
    • Desde campfire.int se quiere proxyear el tráfico a través de vuln-server.int
  • La configuración de /etc/proxychains.conf debe coincidir con el puerto del comando SSH
    • socks5: hace que proxychains use SOCKS5
    • 127.0.0.1: usa localhost
    • 8080: debe coincidir con el puerto especificado en SSH -D
  • Comando a usar
    • ssh -N -f -D 8080 root@vuln-server.int
  • Después de crear el forwarding, si se configura socks5 127.0.0.1 8080, se puede acceder a la página web interna con proxychains curl 192.168.1.185
  • El DNS a través de SOCKS puede no funcionar bien según el entorno, por eso en el ejemplo se usa la dirección IP en lugar del nombre de host
  • Firefox también puede usar un proxy SOCKS mediante la configuración manual de proxy
    • Ruta: Settings → Privacy & Security → Network Settings
    • Seleccionar Manual proxy configuration
    • Marcar “Proxy DNS when using SOCKS V5”
    • Configurar SOCKS host como 127.0.0.1 y port como 8080

Jump host -J

  • Un jump host permite conectarse a un destino al que no se puede acceder directamente desde el host actual, pasando por varios saltos SSH
  • La cadena de ejemplo es campfire.intvuln-server.intinternal-web.intdns.int
  • Comando a usar
    • ssh -J root@vuln-server.int,root@internal-web.int root@dns.int
  • Varios destinos de salto se separan con comas

Agent forwarding -A

  • ssh-agent permite agregar llaves privadas o identidades en la máquina local con ssh-add <private_key_file>
  • Las llaves agregadas se pueden ver con ssh-add -l
  • Al agregar una llave a ssh-agent, se puede conectar por SSH con esa llave sin volver a ingresar la contraseña, lo que es útil tanto para personas como para cuentas de servicio
  • El agent forwarding -A permite usar las llaves del agente local también desde la máquina remota a la que se accedió
  • Antes de usarlo, para revisar los riesgos de seguridad, conviene consultar Zero Effort Private Key Compromise: Abusing SSH-Agent for Lateral Movement
  • Comando de ejemplo
    • ssh -A -J root@vuln-server.int root@internal-web.int
  • Este comando permite conectarse a internal-web.int pasando por vuln-server.int, y usar las llaves que están en el agente SSH local de campfire.int
  • Luego, al ejecutar ssh root@dns.int desde internal-web.int, se puede conectar sin especificar una llave privada ni ingresar credenciales

Asignación de comandos con TTY -t

  • La opción -t es útil para ejecutar rápidamente comandos que requieren interacción en un servidor remoto
  • Ejemplos son comandos que necesitan TTY, como Vim o top
  • Comando a usar
    • ssh root@internal-web.int -t top
  • Al ejecutarlo, se recibe una TTY en el servidor remoto que incluye el comando top

-g para permitir que hosts externos se conecten al puerto con forwarding local

  • La opción -g permite que hosts remotos se conecten al puerto con forwarding local
  • Es similar al port forwarding local -L, pero la diferencia es que también permite que máquinas externas usen ese puerto “local”
  • Escenario de ejemplo
    • Se tiene acceso a una shell en vuln-server.int
    • Se quiere proxyear las conexiones que llegan al puerto 2222 de vuln-server.int hacia el puerto 22 de internal-web.int
  • Comando a usar
    • ssh -N -f -g -L 2222:localhost:22 root@internal-web.int
  • Interpretación de las opciones
    • -g: permite que hosts remotos se conecten al puerto con forwarding local
    • -L: especifica forwarding local
  • Aunque se haya entrado por SSH al puerto 2222 de vuln-server.int, la shell real estará en internal-web.int

Consola SSH ~? y forwarding durante una sesión

  • La consola SSH es una función oculta que permite controlar SSH en sí, sin interactuar directamente con el sistema remoto
  • Es útil cuando la shell se rompió o cuando hay que controlar la propia sesión SSH
  • La consola de ayuda se puede abrir con ~?
  • Opciones útiles
    • ~.: termina la sesión SSH actual
    • ~C: abre la consola SSH y permite agregar opciones de forwarding
  • Incluso si ya se está conectado a vuln-server.int con un comando ssh normal, al presionar ~C e ingresar -D 8080, esa sesión se puede usar como una sesión de forwarding dinámico
  • Si en campfire.int /etc/proxychains.conf está configurado para usar el puerto 8080, se puede aprovechar proxychains como si se hubiera iniciado desde el principio con ssh -D

Archivo de configuración SSH ~/.ssh/config

  • El archivo de configuración SSH está en ~/.ssh/config y ahorra tiempo al guardar opciones SSH que se ingresan repetidamente
  • Al conectarse por SSH, este archivo se parsea y, si existe una configuración host que coincide con el destino, se usan esas opciones
  • Los argumentos de línea de comandos tienen prioridad sobre el archivo de configuración
    • Por ejemplo, aunque en el archivo de configuración se haya definido que el usuario de internal-web.int sea root, si se ejecuta ssh graham@internal-web.int, se intentará iniciar sesión como graham
  • Ejemplo de configuración básica
# You can put comments with a `#` at the beginning of the line only.
host internal-web.int
    User root
    IdentityFile /home/smores/ssh_agent/internal-web-no-pw
    Port 2222
  • Flujo de procesamiento al ejecutar ssh internal-web.int
    • Hace coincidir internal-web.int de la línea de comandos con host internal-web.int en ~/.ssh/config
    • Si coincide, toma del archivo de configuración las opciones no especificadas en la línea de comandos
    • Si no coincide, usa solo las opciones definidas en la línea de comandos

Palabras clave comunes de SSH config

  • IdentityFile /path/to/private_key
    • Especifica la llave privada que se usará para el host
    • Cumple el mismo rol que ssh -i
  • ForwardAgent
  • ProxyJump root@internal-web.int
    • Especifica el servidor por el que se proxyeará el tráfico
    • Cumple el mismo rol que la opción -J
    • En el ejemplo, muestra que primero se pide autenticación para vuln-server.int, lo que indica que el tráfico pasa por ese host
  • Match
    • Aplica palabras clave de SSH config según condiciones
    • En el ejemplo, si el código de salida del comando export | grep PROXYME=TRUE es 0, se usa el ProxyJump debajo del bloque Match
    • Si no existe la variable de entorno PROXYME, solo se usa el bloque normal host internal-web.int
    • Después de configurar export PROXYME=TRUE, si se ejecuta el mismo ssh internal-web.int, primero se pasa por la autenticación de vuln-server.int y luego se obtiene una shell en internal-web.int
  • scp y algunas utilidades basadas en SSH normalmente también pueden usar SSH config
    • En entornos donde no se use automáticamente, se puede indicar explícitamente con -F ~/.ssh/config

Copiar la llave pública con ssh-copy-id

  • ssh-copy-id es una pequeña utilidad para subir rápidamente una llave pública a un servidor
  • Comando a usar
    • ssh-copy-id -i internal-web root@internal-web.int
  • Interpretación de las opciones
    • -i internal-web: especifica el nombre de la llave privada que se usará para autenticar contra el servidor
    • root@internal-web.int: especifica el servidor al que se subirá la llave pública

Generación e inspección de llaves con ssh-keygen

  • ssh-keygen es una utilidad para generar pares de llaves privada y pública
  • En general se recomienda especificar un tamaño de llave mayor con la opción -b
  • El tamaño de llave predeterminado en el entorno de ejemplo era 3072
  • El algoritmo predeterminado es RSA, pero se puede especificar otro algoritmo con el flag -t
    • Ejemplo: ssh-keygen -t ecdsa -b 521
  • La huella digital y el tamaño en bytes de una llave se pueden consultar con el siguiente comando
    • ssh-keygen -lf <file-name>

1 comentarios

 
GN⁺ 2023-08-24
Opiniones de Hacker News
  • Aquí falta una directiva sorprendentemente simple: configurar algo como AuthorizedKeysCommand /usr/bin/php /etc/ssh/auth.php %u en sshd_config, y hacer que el script obtenga https://github.com/{$user}.keys de GitHub
    Claro que no es código de calidad para producción, pero muestra la idea central
    Después de verificar si pertenece a una organización/grupo de GitHub, si el usuario existe y está mapeado con algo como nss-ato, se le puede permitir iniciar sesión en el servidor
    Al incorporar o dar de baja personas, basta con agregarlas o quitarlas de un grupo de GitHub para otorgar o revocar acceso a los equipos, reduciendo la fricción

    • Amazon Linux hace algo parecido, aunque probablemente sea mucho más complejo por ser de calidad para producción
      En Amazon Linux 2 y versiones anteriores, se invoca la línea de comandos de openssl para revisar el formato de cada clave en el archivo authorized_keys, pero estaba hardcodeado para RSA, así que aunque la versión de OpenSSH admitiera ed25519, no era posible autenticarse con ed25519 en hosts con Amazon Linux 2
      En teoría está bien porque habilita una función interesante¹, pero en la práctica rompe la funcionalidad básica incluso para quienes no usan esa función, lo que hace que confíe menos en Amazon Linux
      La primera vez que me encontré con este problema estaba intentando conectarme por SSH a una máquina administrada por un colega DevOps cloud-first, y como no podía tocarla directamente era difícil diagnosticarlo
      Él conocía bien AWS, pero no tanto Linux, así que no sabía dónde mirar; eligió Amazon Linux pensando que, al ser una distribución creada por el dueño de la plataforma cloud, sería “más compatible”, pero aquí “más compatible” en realidad significaba “con más sorpresas tontas”
      ¹ https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/connect-...
    • Ver cosas así me hace lamentar estar del lado de redes
      Como “la red no funciona bien” es el alcance de mi trabajo, termino perdiéndome funciones interesantes como esta
    • Para este caso de uso, mejor conviene usar certificados SSH
  • Parece que mucha gente no sabe que el parser de configuración de OpenSSH ignora directivas duplicadas, y que solo tiene efecto la primera de una misma directiva
    En parsers de configuración o motores de reglas, muchas veces una directiva posterior sobrescribe a la anterior, así que esto resulta bastante contraintuitivo
    Puede parecer un detalle menor, pero al cambiar valores predeterminados como los de /etc/ssh/sshd_config, las personas y el software suelen agregar los cambios al final del archivo o del bloque de directivas, y esperan que se apliquen
    Empresas de seguridad, organizaciones y varios productos de bastión SSH también se equivocan en esto; las recomendaciones de CIS Benchmarks y la mayoría de las herramientas de auditoría CIS de terceros tampoco consideran la prioridad o la manejan mal
    Para comprobar que las directivas de configuración de OpenSSH quedaron definidas como se espera, no hay que revisar manualmente el archivo de configuración, sino volcar la configuración interna derivada con sshd -T o ssh -G

    • El archivo sudoers funciona de la misma manera
      En software de autenticación/autorización de usuarios, considero que este comportamiento es deseable
      Porque facilita crear configuraciones que no puedan sobrescribirse agregando un archivo nuevo en un directorio whatever.conf.d
      Basta con definir esa configuración antes de cargar whatever.conf.d/* desde el archivo principal y ponerle una protección especial a ese archivo
      Incluso cuando nadie intenta saltarse los controles, esto ayuda a la gestión de configuración, porque si una persona nueva que no conoce todo el contexto agrega un archivo, o si algún paquete instala valores predeterminados raros para su servicio, la configuración base mantiene prioridad
      La razón por la que en otros contextos se ve más seguido el comportamiento contrario es que lo que se quiere no es una “configuración base”, sino valores predeterminados en sentido estricto, usados cuando el usuario/desarrollador/administrador no configuró algo explícitamente
    • Entiendo que algunas directivas pueden repetirse. Por ejemplo, AllowUsers
      Aunque ayer NixOS cambió de repente el orden de fusión y mi AllowUsers terminó debajo de un Match de otro archivo, quedando bloqueado
  • La frase del artículo que dice que con -L en el reenvío local lo local está a la izquierda de la dirección, y que con -R en el reenvío remoto el puerto local está a la derecha de la dirección, es la explicación más importante y concisa
    Desde el principio -L y -R me confundieron, y que cuál instancia de puerto es local cambie según L/R es bastante molesto
    Entiendo que -L y -R cambian la dirección prevista, es decir, dónde están el iniciador y el respondedor, pero habría estado bien que port:address:port siempre significara local:binding:remote, y que -L/-R decidieran qué lado escucha y qué lado envía

    • Lo más fácil es recordar que -L escucha en el puerto local del número que viene inmediatamente después, y -R escucha en el puerto remoto del número que viene inmediatamente después
      El resto, host:port, es solo el formato habitual para indicar a dónde conectarse
      Como se trata de reenvío de puertos a través de un túnel SSH, también se deduce naturalmente que el host se contacta desde el lado opuesto del túnel al que tiene el puerto en escucha
  • Una función poco conocida pero útil en SSH es la multiplexación de conexiones.
    Permite reutilizar una conexión existente en lugar de crear una nueva conexión TCP y volver a pasar por el proceso de autenticación.
    El protocolo en sí tiene el concepto de canales, y cada frame de datos lleva metadatos para distinguir distintos streams; esta función usa eso.
    Una gran ventaja es que, en las sesiones posteriores, no hace falta repetir toda la autenticación.
    Es especialmente buena cuando no hay tmux u otra herramienta similar en el remoto y usas varias ventanas de terminal como varios paneles; se nota aún más si la autenticación incluye procedimientos que toman unos segundos, como una frase de contraseña o tocar un HSM.
    También existe una configuración de “persistencia de conexión”, para no tener que autenticarse cada vez que vas y vienes entre algunos servidores.
    En general, lo veo como una función que está bien tener, pero no como algo que te cambie la vida.
    En algunos servidores esta función está desactivada, y su ausencia se nota más cuando está desactivada que su presencia cuando está activada.
    Más información: https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing

    • Si hay mucha latencia entre el cliente y el servidor, la diferencia es enorme.
      SSH, a diferencia de TLS, que está optimizado para reducir la cantidad de viajes de ida y vuelta, es un protocolo bastante conversador, y esta opción de multiplexación es casi la única excepción.
    • Puede hacer que Ansible sea mucho más cómodo de usar al pasar por un host bastión con ProxyJump.
  • Si tienes muchos hosts en ~/.ssh/config, puedes evitar que el archivo se vuelva demasiado desordenado con la directiva Include, que admite comodines.
    Por ejemplo, en ~/.ssh/config puedes poner Include config.d/*.conf y separar la configuración de host, hostname y user en archivos como ~/.ssh/config.d/work.conf o client1.conf.

    • La directiva Host también admite comodines.
      Por ejemplo, puedes agregar host *_work y poner ahí la configuración común para todos los hosts de trabajo, como host1_work.
    • Otro truco es que puedes poner Include dentro de una directiva Host/Match.
      Por ejemplo, si en ~/.ssh/config pones Host proj1.*.corp e Include ~/.ssh/proj1.conf, puedes mantener los matches por proyecto cerca de la parte superior y reducir la necesidad de revisar muchísimos archivos individuales.
  • Al hacer forwarding, casi nunca uso -f.
    Puede convertirse en un disparo en el pie, porque dificulta saber qué forwarding sigue abierto y funcionando.
    -t es un truco genial, una función que no conocía.
    Un punto importante que es fácil pasar por alto en la lista de comandos de escape con ~ es que también puedes anidar escapes dentro de sesiones anidadas.
    Es útil cuando, por cualquier motivo, no usas -J.
    La lista coincide bien con cosas útiles, y también aprendí algunas más.

    • -t es excelente. Yo lo uso como ssh -t my-dev-vps 'tmux new-session -A -s main', para volver directamente a la posición anterior de mi sesión de tmux cada vez que lo ejecuto.
    • Si tienes varios shells abiertos en el servidor de destino, sigue existiendo el mismo problema.
      Me gustaría que SSH expusiera el estado del forwarding de una manera razonable, en lugar de tener que revisar manualmente la lista de procesos.
  • Hay un pull request actual que agrega soporte para AF_UNIX; si entra, permitirá todo tipo de forwarding interesante.
    Porque será más fácil proxyear una conexión SSH a través de un proceso local arbitrario, y ese proceso podrá encargarse como quiera de llevar los datos hasta el extremo remoto.
    https://github.com/openssh/openssh-portable/pull/431

    • Lo que me interesa es -D usando AF_UNIX, pero está bueno que todo pueda funcionar sobre AF_UNIX.
      Desde hace aproximadamente un año, parece que curl puede usar SOCKS sobre AF_UNIX mediante la sintaxis de ALL_PROXY socks5://localhost/path o socks5h.
      Parece que se agregó porque Tor usa proxies SOCKS sobre AF_UNIX.
      Estaría bueno poder configurar el acceso a la red con permisos Unix estándar y, personalmente, mejor aún si TCP/IP pudiera salir por completo del kernel.
  • La primera vez que vi la consola de SSH me impactó.
    Un colega me mostró ~#, y se sintió como descubrir un menú secreto de trucos que saldría en un juego de SEGA Genesis.

  • ¿Por qué la tilde? Porque la usaban rlogin y rsh.
    ¿Por qué rlogin y rsh usaban la tilde? Porque la usaba cu.
    ¿Por qué cu? Si tenías un módem o una línea serial, te comunicabas con cu, y tenías que enviar códigos Hayes; pero si usabas la secuencia de escape de los códigos Hayes, terminabas saliendo al módem, así que hacía falta una señal separada para escapar de cu.
    ¿Por qué no ^[? Porque eso es de telnet.
    Entonces, si te conectabas a un host por telnet y luego te conectabas a un módem con cu, necesitabas una sintaxis de escape aparte para volver a cu sin salir a telnet.
    Al final, es una estructura de sintaxis de escape apilada sin fin.
    Y, en realidad, no es una tilde, sino una tilde.

    • Yo pensaba que era en el orden CR, tilde, .; ¿la estuve usando mal todo este tiempo?
  • La sección de ssh-copy-id empieza explicando que el comando sube la clave pública, pero de pronto cambia a decir que sube la clave privada; parece un typo.
    Además, este comando no solo sube la clave, sino que la agrega a ~/.ssh/authorized_keys, así que es mucho más útil.
    Por último, en la sección de ssh-keygen, por lo que he leído últimamente, hoy en día se prefiere ed25519 por encima de ecdsa.