Shell a través de WebSocket | Shell through WebSocket
Repo: https://github.com/umarquez/100DaysOfC0D3/tree/master/26-ws-shell
Volvemos a los temas de seguridad, en esta ocasión vamos a tratar de levantar una shell y conectarla a un WebSocket con el fin de poder administrar un servidor tratando de evadir cualquier firewall que nos impida conectarnos de forma directa al puerto ssh, o como forma alternativa de contar con una consola para realizar tareas automatizadas.
Por supuesto deberemos proteger el acceso a la consola, pues no queremos que cualquiera que descubra el end-point tenga acceso a ella; para esto vamos a utilizar una técnica llamada web knocking que es una variante del port knocking, la cual consiste en realizar ciertas peticiones que servirán para validar el acceso antes de hacer disponible el servicio al cliente.
Es importante aclarar que la forma en que manejaremos los datos no es seguro en lo más mínimo pues el objetivo no es desarrollar un servicio de administración, sino demostrar que es posible obtener una shell a través de WebSockets de manera sencilla por lo que deberemos considerar varios puntos si nuestro objetivo fuera desarrollar algo más formal basado en esto.
Motivación
La naturaleza del WebSocket es el evitar los problema que existían en las aplicaciones web que requerían conectarse a servicios en puertos diferentes al http(80)/https(443); de alguna forma es una especie de socket dentro de un socket.
Una herramienta muy utilizada en seguridad es Netcat:
Netcat es una herramienta de red que permite a través de intérprete de comandos y con una sintaxis sencilla abrir puertos TCP/UDP en un HOST (quedando netcat a la escucha), asociar una shell a un puerto en concreto (para conectarse por ejemplo a MS-DOS o al intérprete bash de Linux remotamente) y forzar conexiones UDP/TCP (útil por ejemplo para realizar rastreos de puertos o realizar transferencias de archivos bit a bit entre dos equipos). https://es.wikipedia.org/wiki/Netcat
Una parte de muchos exploits son shellcodes que dirigen la entrada/salida de la shell a un puerto tcp en espera de conexiones o bien, realizan una conexión a un hots/puerto específico y una vez establecida redirige la entrada/salida a la shell.
Golang nos permite ejecutar comandos en el sistema operativo, redirigiendo la entrada/salida de este, además cuenta con distintas interfaces que permiten manejar distintos flujos de datos como los mismo puertos tcp, buffers o archivos como readers/writers, esto facilita el reenvío de datos entre ellos.
Primera prueba
Deberemos poder ejecutar una consola local desde un programa en go a la que redirigiremos su entrada/salida para poder interactuar con ella y comprobar que es posible ejecutar comandos, por ejemplo.
go run main.go
Como lo muestra la imagen, hemos logrado lanzar una consola y controlarla mediante nuestra aplicación.
Siguiente paso: El WebSocket
Ahora, deberemos crear una instancia de la shell y redirigirla a un WS cada vez que se realice una petición, para ello necesitaremos crear un servidor web que escuche las peticiones, inicialice el socket, ejecute la shell y redirija la entrada/salida de esta al socket; además de esto necesitaremos crear un cliente que establezca la conexión al servidor y nos permita interactuar con la consola.
server.go
client.go
¡Parece que funciona correctamente! pero, no podríamos dejar que cualquiera obtenga una consola de nuestro sistema operativo así que vamos a asegurar de alguna forma para evitar que cualquiera se conecte o siquiera detecte que este servicio existe.
Web Knocking
Compliquemos un poco las cosas, ya que contamos con un servidor web podremos recibir información mediante una ruta específica y simular que la página no está disponible; vamos a aprovecharnos de esto para autenticar al usuario de la siguiente manera:
- Obtendremos un token enviando una petición POST a la ruta
/login
, este lo utilizaremos como ruta de la siguiente petición. - A continuación, enviaremos la contraseña utilizando POST a la ruta
/<token>
y sabremos si esta es correcta enviando una petición GET a la misma ruta, esta deberá retornar la cadena OK como resultado además del estatus 200 cualquier otro resultado indicará que esta es incorrecta. Podremos restablecer el valor almacenado enviando una petición DELETE. - Ya que hayamos enviado y validado la contraseña podremos conectarnos a la ruta definida
/shell
por WS para obtener la consola, en caso de contar con una contraseña incorrecta simplemente no generaremos el socket y terminaremos de procesar la petición.
server.go
client.go
Probemos conectándonos a un equipo remoto,
Linux remoto
Mac dentro de la LAN
Notas finales
- Podríamos utilizar algún tipo de caché para almacenar las sesiones por cierto periodo de tiempo, si estas no se convierten en una conexión durante un tiempo determinado, podríamos deshacernos de ellas.
- Deberíamos eliminar la sesión una vez que realicemos la conexión a la consola pues esta no debería poder ser reutilizada.
- Una buena práctica sería utilizar HTTPS para proteger las comunicaciones, además podríamos implementar algún tipo de encripción sobre el WS para agregar una capa adicional de seguridad.
- Tanto los lokens como la manera de manejarlos podría mejorar haciendo uso de JWT y enviando el ID como parte de este, por ejemplo.
- NUNCA deberemos colocar las contraseñas en texto plano o incluirlas como parte del código, esto solo es un ejemplo por lo que no debería implementarse en producción de esta forma.