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.
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-protectorLa ú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-protectorO 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.
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.