- 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.int → vuln-server.int → internal-web.int → dns.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
Opiniones de Hacker News
Aquí falta una directiva sorprendentemente simple: configurar algo como
AuthorizedKeysCommand /usr/bin/php /etc/ssh/auth.php %uensshd_config, y hacer que el script obtengahttps://github.com/{$user}.keysde GitHubClaro 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 servidorAl 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
En Amazon Linux 2 y versiones anteriores, se invoca la línea de comandos de
opensslpara revisar el formato de cada clave en el archivoauthorized_keys, pero estaba hardcodeado para RSA, así que aunque la versión de OpenSSH admitieraed25519, no era posible autenticarse coned25519en hosts con Amazon Linux 2En 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-...
Como “la red no funciona bien” es el alcance de mi trabajo, termino perdiéndome funciones interesantes como esta
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 apliquenEmpresas 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 -Tossh -Gsudoersfunciona de la misma maneraEn 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.dBasta con definir esa configuración antes de cargar
whatever.conf.d/*desde el archivo principal y ponerle una protección especial a ese archivoIncluso 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
AllowUsersAunque ayer NixOS cambió de repente el orden de fusión y mi
AllowUsersterminó debajo de unMatchde otro archivo, quedando bloqueadoLa frase del artículo que dice que con
-Len el reenvío local lo local está a la izquierda de la dirección, y que con-Ren el reenvío remoto el puerto local está a la derecha de la dirección, es la explicación más importante y concisaDesde el principio
-Ly-Rme confundieron, y que cuál instancia de puerto es local cambie según L/R es bastante molestoEntiendo que
-Ly-Rcambian la dirección prevista, es decir, dónde están el iniciador y el respondedor, pero habría estado bien queport:address:portsiempre significaralocal:binding:remote, y que-L/-Rdecidieran qué lado escucha y qué lado envía-Lescucha en el puerto local del número que viene inmediatamente después, y-Rescucha en el puerto remoto del número que viene inmediatamente despuésEl resto,
host:port, es solo el formato habitual para indicar a dónde conectarseComo 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
tmuxu 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
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.
ProxyJump.Si tienes muchos hosts en
~/.ssh/config, puedes evitar que el archivo se vuelva demasiado desordenado con la directivaInclude, que admite comodines.Por ejemplo, en
~/.ssh/configpuedes ponerInclude config.d/*.confy separar la configuración dehost,hostnameyuseren archivos como~/.ssh/config.d/work.confoclient1.conf.Hosttambién admite comodines.Por ejemplo, puedes agregar
host *_worky poner ahí la configuración común para todos los hosts de trabajo, comohost1_work.Includedentro de una directivaHost/Match.Por ejemplo, si en
~/.ssh/configponesHost proj1.*.corpeInclude ~/.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.
-tes 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.
-tes excelente. Yo lo uso comossh -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.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
-DusandoAF_UNIX, pero está bueno que todo pueda funcionar sobreAF_UNIX.Desde hace aproximadamente un año, parece que
curlpuede usar SOCKS sobreAF_UNIXmediante la sintaxis deALL_PROXYsocks5://localhost/pathosocks5h.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
rloginyrsh.¿Por qué
rloginyrshusaban la tilde? Porque la usabacu.¿Por qué
cu? Si tenías un módem o una línea serial, te comunicabas concu, 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 decu.¿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 acusin 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.
CR, tilde,.; ¿la estuve usando mal todo este tiempo?La sección de
ssh-copy-idempieza 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 prefiereed25519por encima deecdsa.