Ver destacados

Vulnerabilidad de Buffer Overflow

En este tutorial se va a mostrar un ejemplo de desbordamiento de buffer para que entiendas por qué sucede y los daños que puede causar.
Escrito por
10.2K Visitas  |  Publicado ago 19 2016 13:40
Favorito
Compartir
Comparte esta pagina a tus Amigos y Contactos usando las siguientes Redes Sociales


En este tutorial vamos a hablar del desbordamiento de buffer (Buffer Overflow), un fallo que lleva mucho tiempo existiendo, ocurre cuando no se comprueban de manera correcta los datos que se copian en una zona de memoria (que ha sido reservada previamente), puede ser que la aplicación funcione correctamente si el usuario inserta datos con un tamaño adecuado, pero si nosotros reservamos memoria para 15 caracteres y el usuario nos inserta 20, afectará a otro área de memoria, que puede estar reservada o no.

 

Esto puede hacer que nuestro programa se cuelgue, pero también puede ser mucho peor, un usuario con malas intenciones puede aprovecharse de este error e influir en el funcionamiento de la aplicación o ejecutar código arbitrario en un equipo (normalmente ese código nos abrirá un intérprete de comandos). Además si el programa corre con privilegios elevados tenemos un grave fallo de seguridad. Otro ataque con el que se puede alterar el funcionamiento de una aplicación o inyectar código es XSS.

 

Nota
Las ejecuciones que verás a lo largo en este tutorial han sido realizadas en el sistema operativo Ubuntu 16.04 de 32 bits.

 

Vamos a ver un ejemplo sencillo de código en C que es vulnerable a este ataque, al lanzar el programa debemos pasar un parámetro, la aplicación espera recibir una cadena no superior a 15 caracteres, si es la cadena esperada será un acceso exitoso, si no se le “denegará”. El código es el que se muestra a continuación:

#include <stdlib.h>
#include <stdio.h>


#define password "Test"

void test(char *str){
    char buffer[15];
    int n = 0;

    strcpy(buffer, str);
    if(strcmp(buffer, password) == 0){
        n = 1;
    }

    if(n){
        printf("Success\n");
        exit(0);
    }else{
        printf("Access denied\n");
    }
}

int main(int argc, char *argv[]){
    if (argc < 2){
        printf("The app requires a parameter\n");
        exit(-1);
    }
    
    test(argv[1]);
}
El programa tiene el nombre de desbordar.c, y para compilar se ha usado lo siguiente:
gcc desbordar.c -o desbordar -fno-stack-protector
La última parte: -fno-stack-protector se usa para que el compilador no ponga protección y podamos mostrar el ejemplo. Si el usuario mete datos correctos, que es una cadena de un máximo 15 caracteres, el programa funciona bien, si metemos una “contraseña” incorrecta nos mostrará Access denied, y si metemos “Test” nos pondrá Success. Veamos una captura ejecutando el programa 2 veces, una con acceso incorrecto y otra con el String correcto:

 

 

Vemos que todo funciona correctamente. Pero y si insertamos una cadena superior, veamos que pasa:

 

 

Hemos lanzado el programa con 20 letras A, y nos muestra Success. En esta aplicación no tenemos nada, simplemente se sale de la aplicación, pero hemos accedido a un área restringida sin conocer la contraseña. Si sustituimos la siguiente función:

strcpy(buffer, str);
Por la siguiente:
strncpy(buffer, str,15);
Y ejecutamos el código con 20 letras A, tenemos la siguiente salida:

 

 

También puedes ver que hacemos uso de strcmp, en su lugar deberíamos utilizar strncmp, así controlamos también el tamaño. Hemos controlado que solo se puedan copiar un máximo de 15 caracteres, por lo que no afecta a nuestro programa que inserten más. Si después de mostrar el mensaje Success ejecutamos un comando de sistema (en este caso whoami), obtenemos la información:

 

 

Arriba no somos root, pero si lo ejecutamos con sudo, obtenemos lo siguiente:

 

 

Lo único que hemos añadido es una línea en el código que vimos arriba, debajo de la línea de código:

printf("Success\n");
Hemos puesto:
system("whoami");
Para entender un poco que ha pasado, voy a modificar el programa para mostrar las 2 variables con las que contamos (buffer y n) tanto si es correcto como no, y a continuación está la salida, la primera insertamos una cadena que será tratada como correcta (“Test”), luego uno incorrecto que no se pasa de longitud y por último las 20 letras A:

 

 

Vemos que en la primera ejecución vale 1 la variable n, porque la cadena pasada es la correcta, en la segunda vale 0, porque es errónea, pero en la última vale 1094795585, lo que hace saltarse la condición que ponemos if(n), será cierto siempre que n sea distinto a 0. No es una buena condición, aunque no tendría por qué fallar si estuviera bien el resto del código. Si metemos 16 letras A cómo parámetro veremos que el valor de la variable n es 65:

 

 

Si miramos el código ASCII, el número 65 corresponde a la letra A, hemos visto que la memoria de la variable n se ha tocado sin quererlo nosotros, esa letra de más que hemos pasado como parámetro ha ido a parar a la variable n. Tendríamos la memoria como sigue:

 

 

Si nos pasamos de caracteres puede ser que nos lance un mensaje de violación de segmento (si eliminamos el exit(0) que tenemos en el if(n)), podemos verlo en la siguiente imagen:

 

 

Esa advertencia se debe a que se ha intentado acceder a una zona de memoria que se encuentra fuera de los límites de la que asigno el sistema operativo a la aplicación. Si compiláramos el ejemplo de la siguiente manera:

gcc desbordar.c -o desbordar -fstack-protector
O simplemente eliminando -fno-stack-protector de la compilación que vimos la primera vez, y ejecutamos el código con desbordamiento obtenemos el siguiente resultado:

 

 

Un extra de protección que nos proporciona gcc.

 

Nota
Si quisiéramos ejecutar un código (shellcode) tendríamos que sobrescribir la dirección de retorno con la de nuestra shellcode, es algo más complejo que el ejemplo visto en el tutorial y por lo tanto conlleva más trabajo.

 

Si alguien consigue aprovecharse de esta vulnerabilidad puede causarte mucho daño. Evitar tener este tipo de fallo y que un usuario malintencionado se pueda aprovechar de esto es muy fácil, programar correctamente, se tiene que conocer bien el lenguaje de programación que se está utilizando, saber que funciones usar y que no usar, testear bien la aplicación, no solo con datos correctos, tiene que funcionar también correctamente cuando tratemos con datos imprevistos.

 

Otros ataques que puedes revisar y estar al tanto para que no te afecten o minimizar sus riesgos son: DoS y Fuerza bruta. Y no olvides de revisar la página CVE para estar al tanto de las vulnerabilidades.

¿Te ayudó este Tutorial?

Ayuda a mejorar este Tutorial!
¿Quieres ayudarnos a mejorar este tutorial más? Puedes enviar tu Revisión con los cambios que considere útiles. Ya hay 0 usuario que han contribuido en este tutorial al enviar sus Revisiones. ¡Puedes ser el próximo!