¿La seguridad de SSH ProxyCommand es más débil de lo que parece?
La seguridad de SSH ProxyCommand exige revisar la ejecución local del shell, las expansiones, las inclusiones y el enrutamiento mediante hosts de salto antes de que los agentes lleguen a un host remoto.

SSH ProxyCommand es ejecución de código local que ocurre antes del comando remoto que pretendías ejecutar. Parece obvio al decirlo, pero en las revisiones se suele tratar como un detalle de transporte: el agente pide ssh host uptime, quien revisa comprueba el host y el comando remoto, y el cliente inicia silenciosamente un comando de shell local que nadie incluyó en la decisión de aprobación.
Esa brecha importa más cuando un agente autónomo de programación puede invocar SSH. El destino remoto puede estar muy restringido, mientras que la configuración SSH local contiene alias, inclusiones, reglas condicionales, un ayudante proxy y una cadena de hosts de salto acumulados durante años. Una revisión segura empieza por la acción del lado del cliente que realiza SSH y luego avanza hacia la red.
ProxyCommand se ejecuta antes de que exista la sesión remota
ProxyCommand indica al cliente SSH que inicie un comando local y use su entrada y salida estándar como transporte hacia el servidor SSH. El comando no se ejecuta en el destino. Se ejecuta en la máquina que inició ssh, con esa cuenta local, antes de que SSH pueda autenticar el servidor previsto.
Una entrada típica parece inocua:
Host build-private
HostName build.internal.example
ProxyCommand /usr/local/bin/connect-private %h %p
Cuando alguien ejecuta ssh build-private, SSH expande %h y %p, y después inicia /usr/local/bin/connect-private build.internal.example 22. El ayudante puede abrir un socket, llamar a otro cliente, leer un archivo de token, cargar una biblioteca, consultar variables de entorno o ejecutar un script de shell. A partir de ese momento, el cliente SSH solo ve un flujo de bytes. Tu solicitud de aprobación puede describir una conexión SSH, mientras que la primera acción importante fue iniciar un programa local.
El manual de ssh_config de OpenSSH es claro sobre el límite: ProxyCommand especifica el comando que se usa para conectar con el servidor y advierte que el comando se ejecuta mediante el shell del usuario. Esta última parte cambia la revisión. Una línea de comandos no es un array de argumentos. El análisis del shell, las expansiones, las redirecciones, las sustituciones de comandos y el entorno de inicio del shell forman parte de la acción.
No restes importancia a esto porque el ayudante viva en un repositorio de dotfiles. Una configuración revisada hace seis meses puede invocar un binario encontrado mediante PATH, cargar un archivo de un directorio con permisos de escritura o depender de un alias que ahora se resuelve de otra forma. El objetivo de la revisión es la ruta de ejecución resuelta en la máquina que la ejecutará.
Un alias de host puede seleccionar mucho más que un host
La configuración de SSH es un sistema de coincidencias, no un diccionario sencillo. Un alias corto puede activar ajustes de la configuración del sistema, la del usuario, fragmentos incluidos, hosts comodín, canonicalización y bloques condicionales. El comando final puede proceder de un archivo que nadie relaciona con el alias.
Empieza enumerando las configuraciones que lee SSH. En un cliente OpenSSH habitual, se incluyen /etc/ssh/ssh_config, los archivos a los que se llega mediante Include y ~/.ssh/config. El empaquetado de la distribución y las opciones de línea de comandos pueden cambiar la lista, así que inspecciona también la invocación. Quien proporciona -F /some/file ha cambiado toda la superficie de revisión.
Usa ssh -G para imprimir la configuración resuelta de un destino concreto. Para un alias de prueba controlado, este es un artefacto útil:
ssh -G build-private | grep -E '^(hostname|user|port|proxycommand|proxyjump|identityfile) '
La salida tiene este aspecto:
hostname build.internal.example
user deploy
port 22
proxycommand /usr/local/bin/connect-private %h %p
identityfile ~/.ssh/id_deploy
ssh -G muestra lo que seleccionó SSH y detecta un número sorprendente de errores: una entrada amplia de Host *, una inclusión antigua o un alias que se asigna a una cuenta distinta de la esperada. No demuestra que el ayudante sea seguro. Tampoco siempre explica todas las condiciones que llevaron a ese resultado de una manera comprensible para quien revisa, así que sigue cada directiva relevante hasta su archivo de origen.
Trata la entrada de host como datos solo si la configuración la mantiene como datos. Si un agente puede enviar alias arbitrarios, puede elegir cualquier bloque Host coincidente que la cuenta local pueda leer. Si puede enviar opciones -o arbitrarias o una ruta de configuración, a menudo puede eludir por completo la revisión del alias. Un contenedor que acepta un comando SSH de formato libre no es un límite real.
Una interfaz mejor acepta un identificador de destino de una lista reducida de permitidos y lo asigna a una cuenta, un nombre de host, un puerto y un método de conexión fijos. El identificador puede ser production-readonly; los argumentos SSH subyacentes no deberían proceder de una cadena generada por un agente.
La expansión del shell convierte la configuración en un límite de entrada
Como SSH pasa ProxyCommand por un shell, las comillas en la configuración no ofrecen la misma seguridad que los argumentos estructurados de un proceso. La sintaxis del shell permanece hasta que este la procesa. Incluye espacios, caracteres comodín, referencias a variables, redirecciones y sustituciones de comandos cuando aparecen en el texto del comando.
El patrón arriesgado es un ayudante que construye su propio comando de shell con los valores que recibe. Considera esta configuración:
Host *
ProxyCommand sh -c 'relay --target %h --port %p --token \"$RELAY_TOKEN\"'
Aquí hay varias preguntas distintas. ¿Todo nombre de host resuelto posible contiene solo la sintaxis esperada para un nombre de host? ¿relay interpreta --target como un único valor? ¿Puede un entorno controlado por el usuario establecer RELAY_TOKEN o alterar la ruta de búsqueda? ¿El shell exterior recibe una cadena de comandos con caracteres que cambian de significado antes de que se inicie relay? Decir que el nombre de host procede de SSH no las responde.
Los tokens con porcentaje no son un sistema de plantillas seguro. OpenSSH expande tokens como %h, %p, %r y %n en muchas opciones. %n es el argumento de host original, mientras que %h es el nombre de host que SSH usará después de procesar la configuración. Es fácil pasar por alto esta diferencia. Un comando proxy que usa %n puede recibir un alias fácil de recordar o texto solicitado arbitrario, cuando quien lo creó esperaba un nombre DNS canónico. Un comando que usa %h aún puede recibir un valor modificado por HostName o la canonicalización.
La solución suele ser menos ingeniosa que la configuración original. Usa un ayudante dedicado pequeño con una ruta de ejecutable fija, dale una sintaxis restringida y haz que rechace cualquier cosa fuera de ella. Haz que el ayudante llame a API de conexión o a un iniciador de procesos con array de argumentos en vez de montar otra línea de shell. Si un script de shell es inevitable, valida las entradas antes de interpolarlas, pon entre comillas cada expansión para ese shell y mantén el conjunto aceptado lo bastante limitado para que una persona auditora pueda probarlo.
No sustituyas el análisis sintáctico por eval. Los equipos recurren a él porque hace que un contenedor guiado por configuración parezca flexible. También convierte una sola comilla olvidada en un defecto de ejecución local. La flexibilidad debe vivir en un formato de configuración revisado, no en un shell que vuelve a interpretar texto.
ProxyJump elimina un shell, no el problema de confianza
ProxyJump, escrito a menudo como -J o ProxyJump, indica a SSH que alcance el destino a través de uno o varios hosts de salto SSH. Para el enrutamiento habitual mediante bastión, úsalo en lugar de un ProxyCommand escrito a mano que solo inicia otro comando ssh -W. Describe la topología prevista y evita incluir esa topología en un comando de shell personalizado.
Por ejemplo:
Host build-private
HostName build.internal.example
User deploy
ProxyJump bastion-admin
Host bastion-admin
HostName bastion.example
User relay
Esto es más fácil de inspeccionar que una cadena con comillas anidadas. Aun así necesita una revisión cuidadosa. El cliente se autentica en el host de salto, el host de salto participa en la ruta, y su nombre, cuenta, clave de host, requisitos de MFA, reglas de reenvío y conectividad de red afectan a la acción. Un host de salto comprometido o mal configurado puede exponer patrones de tráfico y redirigir un intento de conexión. La verificación de claves de host sigue siendo obligatoria en cada salto SSH.
OpenSSH permite ambos ajustes, pero ProxyCommand tiene prioridad sobre ProxyJump cuando se aplican los dos. Es una sorpresa de configuración desagradable. Quien revisa puede ver un ProxyJump limpio en un bloque específico de host, mientras una regla coincidente amplia anterior o posterior proporciona un comando proxy. Verifica el resultado efectivo con ssh -G en vez de deducirlo de un único bloque.
Varios hosts de salto también necesitan un motivo explícito. No trates una cadena como seguridad adicional de forma predeterminada. Cada host añadido implica otro uso de credenciales, otra decisión sobre la clave de host y otro lugar que auditar. Usa una cadena solo cuando la ruta de red lo exija y registra qué cuenta se espera en cada salto.
Include y Match exec pueden ejecutar lógica local durante la selección
Include hace que la configuración SSH sea modular, algo útil hasta que un repositorio, una herramienta de gestión de configuración o un instalador deja un fragmento en un directorio de coincidencia amplia. OpenSSH expande patrones comodín en Include; por tanto, un archivo inesperado puede cambiar los ajustes de un host sin tocar la configuración principal. Revisa la propiedad y los permisos de escritura de los directorios incluidos, no solo su contenido actual.
Match añade condiciones. Las condiciones Match host, user, localnetwork y otras cambian qué opciones se aplican. Match exec merece una advertencia más visible: SSH ejecuta el comando local indicado y usa su estado de salida para decidir si el bloque coincide. Leer la configuración puede, por tanto, invocar un ejecutable local.
Este ejemplo hace visible el límite:
Match exec \"/usr/local/bin/on-corporate-network\"
ProxyJump corp-bastion
La condición puede ser razonable si el ayudante es un programa fijo, propiedad de root y con un resultado sencillo. Se vuelve frágil cuando invoca un shell, lee un archivo de proyecto con permisos de escritura, contacta con un servicio de red o depende de PATH. No pruebes reglas desconocidas de Match exec en una estación de trabajo llena de credenciales activas solo porque pensabas ejecutar ssh -G; inspecciona primero el comando y usa una cuenta o una máquina aislada cuando sea necesario.
El manual de OpenSSH también documenta la expansión de tokens en opciones de coincidencia y relacionadas con comandos. Eso significa que una condición Match exec puede depender del texto solicitado de host o usuario. Mantén los valores elegidos por el agente fuera de esas condiciones. Si la condición necesita datos contextuales, derívalos de un archivo de estado local de confianza con propiedad estricta, no de una cadena que cruzó el límite de un agente.
El incidente suele empezar con un ayudante de conveniencia
Un fallo realista no necesita una configuración que parezca maliciosa. Imagina que un desarrollador añadió esto hace años para llegar a una red de pruebas privada:
Host test-*
ProxyCommand connect-testnet %h %p
connect-testnet se instaló en su día mediante un script de arranque en un directorio bin personal. El script llama a un cliente VPN de la empresa y luego inicia un comando de relé encontrado en PATH. Más tarde, un agente de compilación se ejecuta con la cuenta del desarrollador. Puede pedir ssh test-cache. La acción esperada es un comando de solo lectura en una máquina de pruebas.
Primero, SSH encuentra la coincidencia test-*. Segundo, el shell local inicia connect-testnet. Tercero, el script inicia el cliente VPN y el ejecutable que en ese momento gana la búsqueda en PATH. Solo después de esos pasos locales SSH comienza su protocolo con el host indicado. Una auditoría que registra únicamente el comando remoto pierde el comportamiento que cambió el estado de la máquina local y eligió la ruta de red.
El defecto no consiste en que los scripts de shell estén prohibidos. Consiste en tratar una cadena de conveniencia local como si fuera parte del servicio remoto. La capacidad del agente de elegir test-cache seleccionó código local. Un agente malicioso podría buscar alias que coincidan con patrones amplios, pero un agente normal puede activar el mismo problema al adivinar un nombre que resulta existir.
Corrige la ruta real. Sustituye el comodín por destinos con nombre siempre que sea práctico. Sitúa el ayudante de relé en una ruta absoluta. Elimina las búsquedas mediante PATH dentro de él. Haz que el ayudante acepte únicamente los valores de host y puerto esperados. Saca la gestión de la conexión VPN de un comando proxy por solicitud si no necesita ejecutarse en cada acción SSH. Después, prueba la configuración final con la cuenta y el entorno que usará el agente.
Aprueba el método de conexión, no solo el destino
Un flujo de aprobación debe mostrar información suficiente para que una persona reconozca la acción. ssh deploy@build-private ofrece un contexto incompleto cuando el alias inicia un ayudante proxy o se enruta mediante un bastión. Quien revisa necesita el destino resuelto, la cuenta, el puerto, el método proxy y la ruta de salto. De otro modo aprueba una etiqueta y confía en que la configuración local conserve el significado de la semana pasada.
Separa tres decisiones que los equipos suelen mezclar. Primera, ¿puede este proceso de agente realizar alguna acción SSH? Segunda, ¿puede usar esta identidad SSH específica? Tercera, ¿puede este destino usar este método de conexión local concreto? Un sí a las dos primeras no responde automáticamente a la tercera. El ayudante proxy puede alcanzar una zona de confianza diferente o provocar efectos locales que la conexión directa no provocaría.
Sallyport mantiene las credenciales SSH fuera del agente y usa su ayudante sp-ssh incluido para las acciones SSH, pero eso no convierte una configuración SSH local en una superficie de políticas segura. Mantén reducida la interfaz de destinos del agente e inspecciona toda configuración de cliente o proceso auxiliar que participe antes de que empiece la acción con credenciales.
Para rutas sensibles, exige aprobación en cada uso de la identidad SSH e incluye el destino y la ruta en la descripción de aprobación. Así se detectan un alias modificado, un bastión inesperado o un intento de uso fuera de la ruta habitual de mantenimiento. No hará útil una tarjeta de aprobación imprecisa. La información tiene que estar ahí.
Prueba la ruta resuelta con credenciales desechables
Una revisión de configuración necesita una prueba de ejecución, pero hazla con credenciales que no puedan dañar producción. Usa un host de prueba, una cuenta temporal y un entorno controlado. Captura el árbol de procesos del lado del cliente y los destinos de red si tu sistema operativo lo permite. El objetivo es confirmar qué ejecutable se inicia, qué argumentos recibe y si crea conexiones fuera de la ruta esperada.
Una secuencia de revisión breve basta para la mayoría de los alias de host:
- Ejecuta
ssh -G aliasy guarda los ajustes resueltos dehostname,user,port,proxycommand,proxyjumpe identidad. - Sigue cada
Includey cada bloqueHostoMatchcoincidente que proporcionó esos valores. - Lee cada ejecutable local de la ruta proxy o de coincidencia, incluidos los scripts y sus archivos de configuración.
- Ejecuta la conexión con credenciales desechables y verifica los hosts de salto reales, las claves de host y los procesos locales.
- Revoca la credencial de prueba después de la prueba y registra la ruta esperada junto al alias.
No te bases únicamente en una conexión satisfactoria. El éxito demuestra que los bytes llegaron a un servidor SSH. No demuestra que los transportó el programa correcto, que los recibió el host previsto ni que antes no ocurrió ningún efecto local.
Un poco de fricción es apropiado aquí. Si nadie puede explicar el ejecutable detrás de un ProxyCommand, nadie debería autorizar a un agente a invocar ese alias. Sustitúyelo, elimínalo o mantén la ruta fuera del alcance del agente hasta que alguien se haga responsable.
Una superficie SSH pequeña supera a una configuración de cliente ingeniosa
La configuración SSH más segura de cara a un agente tiene pocos destinos con nombre, identidades fijas, hosts de salto explícitos y ninguna opción de configuración arbitraria. No necesita un lenguaje de políticas de propósito general para lograrlo. Necesita una interfaz limitada y una configuración que siga siendo aburrida al inspeccionarla.
Vuelve a revisar cuando cambie cualquiera de estos elementos: una actualización del cliente SSH, un nuevo script de arranque, un despliegue de gestión de configuración, un directorio de inclusiones añadido, un nuevo entorno de ejecución de agentes o un nuevo host de salto. Estos cambios suelen llegar en repositorios distintos, precisamente por eso la ruta de conexión se deteriora sin que nadie lo note.
Mantén sencillo el criterio final: antes de que se ejecute un comando remoto, deberías poder nombrar cada ejecutable local que inicia SSH, cada host con el que contacta, cada identidad que usa y la persona que aprobó esa ruta. Si no puedes hacerlo para un alias, no está listo para uso autónomo.
FAQ
¿ProxyCommand se ejecuta en la máquina local?
No. SSH inicia ProxyCommand en el cliente antes de tener un transporte hacia el destino. Trata el comando y todos los programas a los que llegue como ejecución local con la identidad de la persona o el agente que ejecuta SSH.
¿Cómo puedo ver qué ProxyCommand usará SSH?
Usa ssh -G alias para inspeccionar el ajuste resuelto de un host con nombre y, después, lee todos los archivos de configuración coincidentes. Haz la inspección desde una cuenta de prueba controlada cuando la configuración use Match exec, ya que esa condición puede ejecutar un comando local mientras SSH lee la configuración.
¿ProxyJump es más seguro que ProxyCommand?
Por lo general, no. ProxyJump expresa un salto SSH sin entregar un comando de shell a tu shell local, por lo que presenta menos riesgos de comillas y expansiones. Aun así, crea un límite de confianza en el host de salto y requiere la misma revisión de claves de host y cuentas.
¿Puede un alias de host SSH provocar inyección de comandos?
Sí, cuando el shell interpreta texto controlado por un atacante como parte de la línea de comandos de ProxyCommand. Los alias de host, los nombres de host canónicos, los nombres de usuario, los puertos, las variables de entorno y la configuración incluida pueden influir en el comando final si no los restringes de forma deliberada.
¿Qué riesgo de seguridad añade un host de salto a SSH?
Un host de salto puede observar y retransmitir el flujo de bytes, y puede convertirse en el punto donde importan sus propias credenciales SSH, su configuración o sus reglas de reenvío. No debería convertirse silenciosamente en un host de administración general solo por comodidad.
¿Por qué Match exec es arriesgado en ssh_config?
Match exec permite que SSH ejecute un comando local para decidir si se aplica la configuración posterior. Sirve para condiciones limitadas y de confianza, pero convierte la lectura de configuración en una ruta de ejecución de código local y merece la misma revisión que un script auxiliar.
¿Debería usar rutas absolutas en ProxyCommand?
Las rutas absolutas evitan que un cambio en PATH seleccione otro ayudante, pero no vuelven seguro a ese ayudante. Aún debes comprobar sus permisos, argumentos, archivos de configuración y las identidades con las que se comunica.
¿Puedo permitir de forma segura que un agente de IA use SSH?
No permitas que el agente proporcione argumentos SSH, alias de host o rutas de configuración arbitrarios. Dale un conjunto reducido de destinos revisados y mantén la mecánica de conexión local fuera de su superficie de entrada.
¿Una bóveda de credenciales SSH hace seguro ProxyCommand?
La autorización de agentes y el aislamiento de secretos controlan quién puede solicitar una acción y quién ve las credenciales. No inspeccionan una configuración SSH local en busca de expansiones de shell, Include o ayudantes ejecutables, así que sigue siendo necesaria una revisión de la configuración.
¿Qué es lo primero que debo auditar en una configuración de cliente SSH?
Primero, enumera los alias de host a los que puede llegar un agente y ejecuta ssh -G para cada uno en un entorno controlado. Elimina las entradas personalizadas de ProxyCommand que no puedas explicar y sustituye las restantes por ayudantes revisados con ruta absoluta o por ProxyJump cuando encaje.