¿Qué es el desbordamiento del búfer?
Los buffers son regiones de almacenamiento de memoria que contienen datos temporalmente mientras se transfieren de una ubicación a otra. Un Buffer Overflow (o desbordamiento del búfer) ocurre cuando el volumen de datos excede la capacidad de almacenamiento del búfer de memoria. Como resultado, el programa que intenta escribir los datos en el búfer sobrescribe las ubicaciones de memoria adyacentes .
Por ejemplo, se puede diseñar un búfer para las credenciales de inicio de sesión para que espere entradas de nombre de usuario y contraseña de 8 bytes, de modo que si una transacción implica una entrada de 10 bytes (es decir, 2 bytes más de lo esperado), el programa puede escribir el exceso. datos más allá del límite del buffer.
¿Te gustaría enterarte de cuando lanzamos descuentos y nuevos cursos?
Los desbordamientos de búfer pueden afectar a todo tipo de software. Por lo general, son el resultado de entradas con formato incorrecto o de no asignar suficiente espacio para el búfer. Si la transacción sobrescribe el código ejecutable, puede hacer que el programa se comporte de manera impredecible y genere resultados incorrectos, errores de acceso a la memoria o fallas.
Se produce un desbordamiento del búfer cuando un programa intenta escribir demasiados datos en el búfer. Esto puede hacer que el programa falle o ejecute código arbitrario. Las vulnerabilidades de desbordamiento de búfer existen sólo en lenguajes de programación de bajo nivel como C con acceso directo a la memoria. Sin embargo, también afectan a los usuarios de lenguajes web de alto nivel porque los marcos suelen estar escritos en lenguajes de bajo nivel.
Ejemplo
El siguiente es el código fuente de un programa en C que tiene una vulnerabilidad de desbordamiento del búfer:
char greeting[5];
memcpy(greeting, “Hello, world!\n”, 15);
printf(greeting);
¿Qué crees que pasará cuando compilamos y ejecutamos este programa vulnerable? La respuesta puede resultar sorprendente: cualquier cosa puede pasar. Cuando se ejecuta este fragmento de código, intentará colocar quince bytes en un búfer de destino que tiene solo cinco bytes de longitud. Esto significa que se escribirán diez bytes en direcciones de memoria fuera de la matriz. Lo que suceda después depende del contenido original de los diez bytes de memoria sobrescritos.
¿Quizás se almacenaron variables importantes allí y acabamos de cambiar sus valores? El ejemplo anterior está roto de una manera tan obvia que ningún programador en su sano juicio cometería tal error. Entonces consideremos otro ejemplo. Supongamos que necesitamos leer una dirección IP de un archivo. Podemos hacerlo usando el siguiente código C:
#include <stdio.h>
#define MAX_IP_LENGTH 15
int main(void) {
char file_name[] = "ip.txt";
FILE *fp;
fp = fopen(file_name, "r");
char ch;
int counter = 0;
char buf[MAX_IP_LENGTH];
while((ch = fgetc(fp)) != EOF) {
buf[counter++] = ch;
}
buf[counter] = '\0';
printf("%s\n", buf);
fclose(fp);
return 0;
}
El error en el ejemplo anterior no es tan obvio. Suponemos que la dirección IP que queremos leer de un archivo nunca excederá los 15 bytes. Las direcciones IP adecuadas (por ejemplo, 255.255.255.255) no pueden tener más de 15 bytes. Sin embargo, un usuario malintencionado puede preparar un archivo que contenga una cadena falsa muy larga en lugar de una dirección IP (por ejemplo, 19222222222.16888888.0.1). Esta cadena hará que nuestro programa desborde el búfer de destino.
¿Qué es un ataque de desbordamiento de búfer?
Los atacantes aprovechan los problemas de desbordamiento del búfer sobrescribiendo la memoria de una aplicación. Esto cambia la ruta de ejecución del programa, desencadenando una respuesta que daña archivos o expone información privada. Por ejemplo, un atacante puede introducir código adicional y enviar nuevas instrucciones a la aplicación para obtener acceso a los sistemas de TI.
Si los atacantes conocen el diseño de la memoria de un programa, pueden alimentar intencionalmente entradas que el búfer no puede almacenar y sobrescribir áreas que contienen código ejecutable, reemplazándolo con su propio código. Por ejemplo, un atacante puede sobrescribir un puntero (un objeto que apunta a otra área de la memoria) y apuntarlo a una carga útil de explotación para obtener control sobre el programa.
Ejemplo de ataque de desbordamiento del buffer de pila
Ahora que sabemos que un programa puede desbordar una matriz y sobrescribir un fragmento de memoria que no debería sobrescribir, veamos cómo se puede utilizar esto para montar un ataque de desbordamiento de búfer. En un escenario típico (llamado desbordamiento del búfer de pila ), el problema se debe (como tantos problemas en la seguridad de la información) a la mezcla de datos (destinados a ser procesados o mostrados) con comandos que controlan la ejecución del programa. En C, como en la mayoría de los lenguajes de programación, los programas se crean utilizando funciones.
Las funciones se llaman entre sí, se pasan argumentos entre sí y devuelven valores. Por ejemplo, nuestro código, que lee una dirección IP de un archivo, podría ser parte de una función llamada readIpAddress, que lee una dirección IP de un archivo y la analiza. Esta función podría ser llamada por alguna otra función, por ejemplo readConfiguration. Cuando readConfigurationllama readIpAddress, le pasa un nombre de archivo y luego la readIpAddressfunción devuelve una dirección IP como una matriz de cuatro bytes. Fig. 1. Los argumentos y el valor de retorno de la función readIpAddress
Durante esta llamada a función, se almacenan tres piezas diferentes de información, una al lado de la otra, en la memoria de la computadora. Para cada programa, el sistema operativo mantiene una región de memoria que incluye una parte llamada pila o pila de llamadas (de ahí el nombre de desbordamiento del búfer de pila ). Cuando se llama a una función, se le asigna un fragmento de la pila.
Marco
Esta pieza de la pila (llamada marco ) se utiliza para:
- Recuerde la línea de código desde la cual se debe reanudar la ejecución del programa cuando se complete la ejecución de la función (en nuestro caso, será una línea específica de la readConfigurationfunción)
- Almacene los argumentos pasados a la función por su llamador (en nuestro caso, supongamos /home/someuser/myconfiguration/ip.txt)
- Almacene el valor de retorno que la función devuelve a su llamador (en nuestro caso, es una matriz de cuatro bytes, digamos (192, 168, 0, 1))
- Almacenar variables locales de la función llamada mientras se ejecuta esta función (en nuestro caso, la variable char[MAX_IP_LENGTH] buf)
El problema
Entonces, si un programa tiene un búfer asignado en el marco de la pila e intenta insertar más datos de los que caben allí, los datos ingresados por el usuario pueden desbordarse y sobrescribir la ubicación de la memoria donde se almacena la dirección del remitente. Contenido del marco de la pila cuando se llama a la función readIPAddress:
Si el problema fue causado por datos de entrada aleatorios mal formados del usuario, lo más probable es que la nueva dirección de retorno no apunte a una ubicación de memoria donde esté almacenado ningún otro programa, por lo que el programa original simplemente fallará. Sin embargo, si los datos se preparan cuidadosamente, pueden provocar la ejecución de código no deseada.
El primer paso para el atacante es preparar datos especiales que puedan interpretarse como código ejecutable y que funcionen en beneficio del atacante (esto se denomina código shell ). El segundo paso es colocar la dirección de estos datos maliciosos en la ubicación exacta donde debería estar la dirección del remitente. El contenido de ip.txt sobrescribe la dirección del remitente:
De hecho, cuando la función lee la cadena de caracteres IP y la coloca en el búfer de destino, la dirección de retorno se reemplaza por la dirección del código malicioso. Cuando finaliza la función, la ejecución del programa salta al código malicioso.
Tipos de ataques de desbordamiento de búfer
Los desbordamientos del búfer basados en pilas son más comunes y aprovechan la memoria de la pila que solo existe durante el tiempo de ejecución de una función.
Los ataques basados en montón son más difíciles de llevar a cabo e implican inundar el espacio de memoria asignado para un programa más allá de la memoria utilizada para las operaciones actuales en tiempo de ejecución.
¿Qué lenguajes de programación son más vulnerables?
C y C++ son dos lenguajes muy susceptibles a ataques de desbordamiento de búfer, ya que no tienen protecciones integradas contra la sobrescritura o el acceso a datos en su memoria. Mac OSX, Windows y Linux utilizan código escrito en C y C++.
Lenguajes como PERL, Java, JavaScript y C# utilizan mecanismos de seguridad integrados que minimizan la probabilidad de desbordamiento del búfer.
Cómo prevenir los desbordamientos del búfer
Los desarrolladores pueden protegerse contra las vulnerabilidades de desbordamiento del búfer mediante medidas de seguridad en su código o utilizando lenguajes que ofrecen protección integrada.
Además, los sistemas operativos modernos tienen protección en tiempo de ejecución.
Tres protecciones comunes:
- Aleatorización del espacio de direcciones (ASLR) : se mueve aleatoriamente por las ubicaciones del espacio de direcciones de las regiones de datos. Normalmente, los ataques de desbordamiento de búfer necesitan conocer la localidad del código ejecutable, y la aleatorización de los espacios de direcciones hace que esto sea prácticamente imposible.
- Prevención de ejecución de datos: marca ciertas áreas de la memoria como no ejecutables o ejecutables, lo que impide que un ataque ejecute código en una región no ejecutable.
- Protección de sobrescritura del controlador de excepciones estructurado (SEHOP) : ayuda a evitar que el código malicioso ataque el manejo de excepciones estructurado (SEH), un sistema integrado para administrar excepciones de hardware y software. De este modo, evita que un atacante pueda hacer uso de la técnica de explotación de sobrescritura SEH. A nivel funcional, una sobrescritura de SEH se logra utilizando un desbordamiento de búfer basado en pila para sobrescribir un registro de registro de excepción, almacenado en la pila de un subproceso.
Además es recomendable:
Mantenerse al día con los últimos informes de errores para sus productos de servidor web y de aplicaciones y otros productos en su infraestructura de Internet. Aplique los parches más recientes a estos productos. Escanee periódicamente su sitio web con uno o más de los escáneres comúnmente disponibles que buscan fallas de desbordamiento del búfer en sus productos de servidor y sus aplicaciones web personalizadas.
Para el código de su aplicación personalizada, debe revisar todo el código que acepta entradas de los usuarios a través de la solicitud HTTP y asegurarse de que proporciona una verificación del tamaño adecuado en todas esas entradas. Esto se debe hacer incluso en entornos que no son susceptibles a este tipo de ataques, ya que entradas demasiado grandes que no se detectan aún pueden causar denegación de servicio u otros problemas operativos.
Las medidas de seguridad en el código y la protección del sistema operativo no son suficientes. Cuando una organización descubre una vulnerabilidad de desbordamiento del búfer, debe reaccionar rápidamente para parchear el software afectado y asegurarse de que los usuarios del software puedan acceder al parche.
Los programadores pueden mitigar el riesgo de ataques de desbordamiento del búfer validando siempre la longitud de la entrada del usuario. Sin embargo, una buena forma general de evitar las vulnerabilidades de desbordamiento del búfer es seguir utilizando funciones seguras que incluyan protección contra desbordamiento del búfer (que memcpy no la incluye). Estas funciones están disponibles en diferentes plataformas, por ejemplo, strlcpy, strlcat, snprintf(OpenBSD) o strcpy_s, strcat_s, sprintf_s(Windows).
Categorización de NVD
CWE-788: Acceso a la ubicación de la memoria después del final del búfer : esto suele ocurrir cuando un puntero o su índice se incrementa a una posición después del búfer; o cuando la aritmética de punteros da como resultado una posición después del búfer.
El desbordamiento de búfer es probablemente la forma más conocida de vulnerabilidad de seguridad del software. La mayoría de los desarrolladores de software saben qué es una vulnerabilidad de desbordamiento de búfer, pero los ataques de desbordamiento de búfer contra aplicaciones heredadas y recientemente desarrolladas siguen siendo bastante comunes. Parte del problema se debe a la amplia variedad de formas en que pueden ocurrir los desbordamientos del búfer, y parte se debe a las técnicas propensas a errores que se utilizan a menudo para evitarlos.
Los desbordamientos de búfer no son fáciles de descubrir e incluso cuando se descubre uno, generalmente es extremadamente difícil de explotar. Sin embargo, los atacantes han logrado identificar desbordamientos de búfer en una asombrosa variedad de productos y componentes.
Exploits
En un exploit clásico de desbordamiento de búfer, el atacante envía datos a un programa, que los almacena en un búfer de pila de tamaño insuficiente. El resultado es que se sobrescribe la información de la pila de llamadas, incluido el puntero de retorno de la función. Los datos establecen el valor del puntero de retorno para que cuando la función regrese, transfiera el control al código malicioso contenido en los datos del atacante.
Aunque este tipo de desbordamiento del búfer de pila sigue siendo común en algunas plataformas y en algunas comunidades de desarrollo, existe una variedad de otros tipos de desbordamiento del búfer, incluido el desbordamiento del búfer de montón y el error de uno por uno, entre otros. Otra clase de falla muy similar se conoce como ataque de cadena de formato . Hay varios libros excelentes que brindan información detallada sobre cómo funcionan los ataques de desbordamiento de búfer, incluidos Building Secure Software, Writing Secure Code y The Shellcoder’s Handbook.
A nivel de código, las vulnerabilidades de desbordamiento del búfer generalmente implican la violación de las suposiciones del programador. Muchas funciones de manipulación de memoria en C y C++ no realizan verificación de límites y pueden sobrescribir fácilmente los límites asignados de los búferes sobre los que operan. Incluso las funciones limitadas, como strncpy(), pueden causar vulnerabilidades cuando se usan incorrectamente. La combinación de manipulación de la memoria y suposiciones erróneas sobre el tamaño o la composición de un dato es la causa principal de la mayoría de los desbordamientos del búfer.
Las vulnerabilidades de desbordamiento del búfer
Las vulnerabilidades de desbordamiento del búfer suelen ocurrir en código que:
- Se basa en datos externos para controlar su comportamiento.
- Depende de las propiedades de los datos que se aplican fuera del alcance inmediato del código.
- Es tan complejo que un programador no puede predecir con precisión su comportamiento.
Desde el descubrimiento de la técnica de ataque de desbordamiento del búfer de pila, los autores de sistemas operativos (Linux, Microsoft Windows, macOS y otros) han intentado encontrar técnicas de prevención:
- La pila se puede hacer no ejecutable, por lo que incluso si se coloca código malicioso en el búfer, no se puede ejecutar.
- El sistema operativo puede aleatorizar el diseño de la memoria del espacio de direcciones (espacio de memoria). Cuando el código malicioso se coloca en un búfer, el atacante no puede predecir su dirección.
- Otras técnicas de protección (por ejemplo, StackGuard) modifican un compilador de tal manera que cada función llama a un fragmento de código que garantiza que la dirección del remitente no haya cambiado.
En la práctica
Incluso si dichos mecanismos de protección dificultan los ataques de desbordamiento del búfer de pila, no los hacen imposibles. Algunas de estas medidas también pueden afectar el rendimiento. Las vulnerabilidades de desbordamiento de búfer existen en lenguajes de programación que, como C, intercambian seguridad por eficiencia y no verifican el acceso a la memoria. En los lenguajes de programación de nivel superior (por ejemplo, Python, Java, PHP, JavaScript o Perl), que se utilizan a menudo para crear aplicaciones web, las vulnerabilidades de desbordamiento del búfer no pueden existir. En estos idiomas, simplemente no se pueden colocar datos sobrantes en el búfer de destino. Por ejemplo, intente compilar y ejecutar el siguiente código Java:
int[] buffer = new int[5];
buffer[100] = 44;
El compilador de Java no le avisará, pero la máquina virtual Java en tiempo de ejecución detectará el problema y, en lugar de sobrescribir la memoria aleatoria, interrumpirá la ejecución del programa.
Desbordamiento de búfer y aplicaciones web
Los atacantes utilizan desbordamientos de búfer para corromper la pila de ejecución de una aplicación web. Al enviar información cuidadosamente diseñada a una aplicación web, un atacante puede hacer que la aplicación web ejecute código arbitrario, apoderándose efectivamente de la máquina.
Las fallas de desbordamiento de búfer pueden estar presentes tanto en el servidor web como en los productos del servidor de aplicaciones que atienden los aspectos estáticos y dinámicos del sitio, o en la aplicación web misma. Es probable que los desbordamientos de búfer que se encuentran en productos de servidor ampliamente utilizados se vuelvan ampliamente conocidos y puedan representar un riesgo significativo para los usuarios de estos productos. Cuando las aplicaciones web utilizan bibliotecas, como una biblioteca de gráficos para generar imágenes, se exponen a posibles ataques de desbordamiento del búfer.
Los desbordamientos de búfer también se pueden encontrar en el código de aplicaciones web personalizadas, e incluso pueden ser más probables dada la falta de escrutinio por el que suelen pasar las aplicaciones web. Es menos probable que se detecten fallas de desbordamiento de búfer en aplicaciones web personalizadas porque normalmente habrá muchos menos piratas informáticos que intenten encontrar y explotar dichas fallas en una aplicación específica.
Si se descubre en una aplicación personalizada, la capacidad de explotar la falla (aparte de bloquear la aplicación) se reduce significativamente por el hecho de que el código fuente y los mensajes de error detallados de la aplicación normalmente no están disponibles para el pirata informático.
Desbordamiento de búfer y los programadores
Sin embargo, incluso los programadores que utilizan lenguajes de alto nivel deberían conocer y preocuparse por los ataques de desbordamiento del búfer. Sus programas a menudo se ejecutan dentro de sistemas operativos escritos en C o utilizan entornos de ejecución escritos en C, y este código C puede ser vulnerable a tales ataques.
Para ver cómo una vulnerabilidad de desbordamiento de búfer puede afectar a un programador que utiliza un lenguaje de programación de tan alto nivel, analicemos CVE-2015-3329 , una vulnerabilidad de seguridad de la vida real descubierta en la biblioteca estándar PHP en 2015. Una aplicación PHP es una colección de archivos *.php . Para facilitar la distribución de dicha aplicación, se puede empaquetar en un único archivo, como un archivo zip , un archivo tar o utilizando un formato PHP personalizado llamado phar .
Una extensión PHP llamada phar contiene una clase que puede utilizar para trabajar con dichos archivos. Con esta clase, puede analizar un archivo, enumerar sus archivos, extraerlos, etc. Usar esta clase es bastante simple.
Ejemplo
Por ejemplo, para extraer todos los archivos de un archivo, utilice el siguiente código:
$phar = new Phar(‘phar-file.phar’);
$phar->extractTo(‘./directory’);
Cuando la Pharclase analiza un archivo (es decir new Phar(‘phar-file.phar’)), lee todos los nombres de archivo del archivo, concatena cada nombre de archivo con el nombre del archivo y luego calcula la suma de verificación. Por ejemplo, para un archivo llamado myarchive.phar que contiene archivos index.php y componentes/hello.php , la clase Phar calcula sumas de verificación de dos cadenas: myarchive.pharindex.php y myarchive.pharcomponents/hello.php .
La razón por la que los autores lo implementaron de esta manera no es importante aquí; lo importante es cómo lo implementaron. Hasta 2015, esta operación se realizaba mediante la siguiente función (ver el antiguo código fuente PHP ):
phar_set_inode(phar_entry_info *entry TSRMLS_DC) /* {{{ */
{
char tmp[MAXPATHLEN];
int tmp_len;
tmp_len = entry->filename_len + entry->phar->fname_len;
memcpy(tmp, entry->phar->fname, entry->phar->fname_len);
memcpy(tmp + entry->phar->fname_len, entry->filename, entry->filename_len);
entry->inode = (unsigned short)zend_get_hash_value(tmp, tmp_len);
}
Como puede ver, esta función crea una matriz char de llamados tmp. Primero, el nombre del archivo phar (en nuestro ejemplo, myarchive.phar ) se copia en esta matriz usando el siguiente comando:
memcpy(tmp,entry->phar->fname,entry->phar->fname_len);
En este comando:
- El primer argumento, tmpes un destino donde se deben copiar los bytes.
- El segundo argumento, entry->phar->fname, es una fuente desde donde se deben copiar los bytes; en nuestro caso, el nombre del archivo ( myarchive.phar ).
- El tercer argumento, entry->phar->fname_lenes una cantidad de bytes que deben copiarse; en nuestro caso, es la longitud (en bytes) del nombre del archivo comprimido.
La función copia el nombre del archivo (en nuestro ejemplo, index.php o componentes/hello.php ) en la tmpmatriz de caracteres usando el siguiente comando:
memcpy(tmp + entry->phar->fname_len, entry->filename, entry->filename_len);
En este comando:
- El primer argumento, tmp + entry->phar->fname_lenes un destino donde se deben copiar los bytes; en nuestro caso, es una ubicación en la tmpmatriz justo después del final del nombre del archivo.
- El segundo argumento, entry->filenamees una fuente desde donde se deben copiar los bytes.
- El tercer argumento, entry->filename_lenes una cantidad de bytes que deben copiarse.
Luego zend_get_hash_valuese llama a la función para calcular el código hash. Observe cómo se declara el tamaño del búfer:
char tmp[MAXPATHLEN];
Tiene un tamaño de MAXPATHLEN, que es una constante definida como la longitud máxima de una ruta del sistema de archivos en la plataforma actual. Los autores asumieron que si concatenan el nombre del archivo con el nombre de un archivo dentro del archivo, nunca excederán la longitud de ruta máxima permitida. En situaciones normales, este supuesto se cumple. Sin embargo, si el atacante prepara un archivo con nombres inusualmente largos, es inminente un desbordamiento del búfer.
La función phar_set_inodeprovocará un desbordamiento en la tmpmatriz. Un atacante puede usar esto para bloquear PHP (provocando una denegación de servicio) o incluso hacer que ejecute código malicioso. El problema es similar a nuestro ejemplo simple de arriba: el programador cometió un simple error, confió demasiado en la entrada del usuario y asumió que los datos siempre cabrían en un búfer de tamaño fijo. Afortunadamente, esta vulnerabilidad fue descubierta en 2015 y solucionada .
Consecuencias
- Categoría: Disponibilidad: los desbordamientos del búfer generalmente provocan fallos. Son posibles otros ataques que provoquen falta de disponibilidad, incluido poner el programa en un bucle infinito.
- Control de acceso (procesamiento de instrucciones): los desbordamientos del búfer a menudo se pueden utilizar para ejecutar código arbitrario, que generalmente está fuera del alcance de la política de seguridad implícita de un programa.
- Otro: cuando la consecuencia es la ejecución de código arbitrario, esto a menudo puede usarse para subvertir cualquier otro servicio de seguridad.
Periodo de exposición
- Especificación de requisitos: Se podría optar por utilizar un lenguaje que no sea susceptible a estos problemas.
- Diseño: Se podrían introducir tecnologías de mitigación, como bibliotecas de cadenas seguras y abstracciones de contenedores.
- Implementación: muchos errores lógicos pueden provocar esta condición. Puede verse exacerbado por la falta o el mal uso de tecnologías de mitigación.
Ambientes afectados
Casi todos los servidores web, servidores de aplicaciones y entornos de aplicaciones web conocidos son susceptibles a desbordamientos de búfer, con la excepción notable de los entornos escritos en lenguajes interpretados como Java o Python, que son inmunes a estos ataques (excepto los desbordamientos en el propio Interpretor).
Plataforma
- Idiomas: C, C++, Fortran, Ensamblador
- Plataformas operativas: Todas, aunque se podrán implementar medidas preventivas parciales, dependiendo del entorno.
Cómo determinar si eres vulnerable
Para productos de servidor y bibliotecas, manténgase actualizado con los últimos informes de errores de los productos que está utilizando. Para el software de aplicación personalizado, todo el código que acepta entradas de los usuarios a través de la solicitud HTTP debe revisarse para garantizar que pueda manejar adecuadamente entradas arbitrariamente grandes.
Ejemplo 1
El siguiente código de ejemplo demuestra un desbordamiento de búfer simple que a menudo es causado por el primer escenario en el que el código depende de datos externos para controlar su comportamiento. El código utiliza la función get() para leer una cantidad arbitraria de datos en un búfer de pila. Debido a que no hay forma de limitar la cantidad de datos leídos por esta función, la seguridad del código depende de que el usuario ingrese siempre menos de caracteres BUFSIZE.
…
char buf[BUFSIZE];
gets(buf);
…
Este ejemplo muestra lo fácil que es imitar el comportamiento inseguro de la función gets() en C++ utilizando el >>operador para leer la entrada en una cadena char[].
…
char buf[BUFSIZE];
cin >> (buf);
…
Ejemplo 2
El código de este ejemplo también se basa en la entrada del usuario para controlar su comportamiento, pero agrega un nivel de direccionamiento indirecto con el uso de la función de copia de memoria limitada memcpy(). Esta función acepta un búfer de destino, un búfer de origen y la cantidad de bytes para copiar. El búfer de entrada se llena mediante una llamada limitada a read(), pero el usuario especifica el número de bytes que copia memcpy().
...
char buf[64], in[MAX_SIZE];
printf("Enter buffer contents:\n");
read(0, in, MAX_SIZE-1);
printf("Bytes to copy:\n");
scanf("%d", &bytes);
memcpy(buf, in, bytes);
…
Nota: Este tipo de vulnerabilidad de desbordamiento del búfer (donde un programa lee datos y luego confía en un valor de los datos en operaciones de memoria posteriores sobre los datos restantes) ha aparecido con cierta frecuencia en bibliotecas de procesamiento de imágenes, audio y otros archivos.
Ejemplo 3
Este es un ejemplo del segundo escenario en el que el código depende de propiedades de los datos que no se verifican localmente. En este ejemplo, una función denominada lccopy()toma una cadena como argumento y devuelve una copia de la cadena asignada al montón con todas las letras mayúsculas convertidas a minúsculas. La función realiza una verificación sin límites en su entrada porque espera que str siempre sea menor que BUFSIZE. Si un atacante pasa por alto las comprobaciones en el código que llama a lccopy(), o si un cambio en ese código hace que la suposición sobre el tamaño de str sea falsa, entonces lccopy()se desbordará buf con la llamada ilimitada a strcpy().
char *lccopy(const char *str) {
char buf[BUFSIZE];
char *p;
strcpy(buf, str);
for (p = buf; *p; p++) {
if (isupper(*p)) {
*p = tolower(*p);
}
}
return strdup(buf);
}
Ejemplo 4
El siguiente código demuestra el tercer escenario en el que el código es tan complejo que su comportamiento no se puede predecir fácilmente. Este código proviene del popular decodificador de imágenes libPNG, que es utilizado por una amplia gama de aplicaciones, incluidas Mozilla y algunas versiones de Internet Explorer.
El código parece realizar la verificación de límites de manera segura porque verifica el tamaño de la longitud variable, que luego usa para controlar la cantidad de datos copiados por png_crc_read(). Sin embargo, inmediatamente antes de probar la longitud, el código realiza una verificación png_ptr->modey, si esta verificación falla, se emite una advertencia y el procesamiento continúa. Debido a que la longitud se prueba en un bloque else if, la longitud no se probará si la primera verificación falla y se usa ciegamente en la llamada a png_crc_read(), lo que potencialmente permite un desbordamiento del búfer de pila.
Aunque el código de este ejemplo no es el más complejo que hemos visto, demuestra por qué se debe minimizar la complejidad en el código que realiza operaciones de memoria.
if (!(png_ptr->mode & PNG_HAVE_PLTE)) {
/* Should be an error, but we can cope with it */
png_warning(png_ptr, "Missing PLTE before tRNS");
}
else if (length > (png_uint_32)png_ptr->num_palette) {
png_warning(png_ptr, "Incorrect tRNS chunk length");
png_crc_finish(png_ptr, length);
return;
}
...
png_crc_read(png_ptr, readbuf, (png_size_t)length);
Ejemplo 5
Este ejemplo también demuestra el tercer escenario en el que la complejidad del programa lo expone a desbordamientos del búfer. En este caso, la exposición se debe a la interfaz ambigua de una de las funciones y no a la estructura del código (como fue el caso en el ejemplo anterior).
La getUserInfo()función toma un nombre de usuario especificado como una cadena multibyte y un puntero a una estructura para información del usuario, y completa la estructura con información sobre el usuario. Dado que la autenticación de Windows utiliza Unicode para los nombres de usuario, el argumento del nombre de usuario primero se convierte de una cadena multibyte a una cadena Unicode. Luego, esta función pasa incorrectamente el tamaño en unicodeUserbytes en lugar de caracteres.
Por lo tanto, la llamada a MultiByteToWideChar()puede escribir hasta (UNLEN+1)*sizeof(WCHAR)caracteres anchos, o (UNLEN+1)*sizeof(WCHAR)*sizeof(WCHAR)bytes, en la matriz unicodeUser, que solo tiene (UNLEN+1)*sizeof(WCHAR)bytes asignados. Si la cadena del nombre de usuario contiene más de UNLENcaracteres, la llamada a MultiByteToWideChar()desbordará el búfer unicodeUser.
void getUserInfo(char *username, struct _USER_INFO_2 info){
WCHAR unicodeUser[UNLEN+1];
MultiByteToWideChar(CP_ACP, 0, username, -1,
unicodeUser, sizeof(unicodeUser));
NetUserGetInfo(NULL, unicodeUser, 2, (LPBYTE *)&info);
}
Ataque de desbordamiento de búfer
Los errores de desbordamiento de búfer se caracterizan por la sobrescritura de fragmentos de memoria del proceso, que nunca debieron haber sido modificados de forma intencionada o no. La sobrescritura de valores de IP (puntero de instrucción), BP (puntero base) y otros registros provoca excepciones, fallas de segmentación y otros errores. Generalmente estos errores finalizan la ejecución de la aplicación de forma inesperada. Los errores de desbordamiento del búfer ocurren cuando operamos con búferes de tipo char.
Los desbordamientos del búfer pueden consistir en desbordar la pila [Stack overflow] o desbordar el montón [Heap overflow]. No distinguimos entre estos dos en este artículo para evitar confusiones. Los siguientes ejemplos están escritos en lenguaje C bajo el sistema GNU/Linux en arquitectura x86.
Ejemplo 1
#include <stdio.h>
int main(int argc, char **argv)
{
char buf[8]; // buffer for eight characters
gets(buf); // read from stdio (sensitive function!)
printf("%s\n", buf); // print out data stored in buf
return 0; // 0 as return value
}
Esta aplicación muy simple lee de la entrada estándar una matriz de caracteres y la copia en el búfer del tipo char. El tamaño de este búfer es de ocho caracteres. Después de eso, se muestra el contenido del búfer y la aplicación se cierra.
Compilación del programa:
user@spin ~/inzynieria $ gcc bo-simple.c -obo-simple
/tmp/ccECXQAX.o: In function `main':
bo-simple.c:(.text+0x17): warning: the `gets' function is dangerous and
should not be used.
En esta etapa, incluso el compilador sugiere que la función gets() no es segura.
Ejemplo de uso:
user@spin ~/inzynieria $ ./bo-simple // program start
1234 // we eneter "1234" string from the keyboard
1234 // program prints out the conent of the buffer
user@spin ~/inzynieria $ ./bo-simple // start
123456789012 // we eneter "123456789012"
123456789012 // content of the buffer "buf" ?!?!
Segmentation fault // information about memory segmenatation fault
Logramos (des)afortunadamente ejecutar la operación defectuosa del programa y provocamos que salga de manera anormal.
Análisis del problema:
El programa llama a una función que opera en el búfer de tipo char y no realiza comprobaciones para evitar que se desborde el tamaño asignado a este búfer. Como resultado, es posible almacenar intencionalmente o no más datos en el búfer, lo que provocará un error. Surge la siguiente pregunta: El buffer almacena sólo ocho caracteres, entonces ¿por qué la función printf() muestra doce? La respuesta proviene de la organización de la memoria de procesos. Cuatro caracteres que desbordaron el búfer también sobrescriben el valor almacenado en uno de los registros, que era necesario para el retorno correcto de la función. La continuidad de la memoria resultó en la impresión de los datos almacenados en esta área de la memoria.
Ejemplo 2
#include <stdio.h>
#include <string.h>
void doit(void)
{
char buf[8];
gets(buf);
printf("%s\n", buf);
}
int main(void)
{
printf("So... The End...\n");
doit();
printf("or... maybe not?\n");
return 0;
}
Este ejemplo es análogo al primero. Además, antes y después de la función doit(), tenemos dos llamadas a la función printf().
Compilation:
user@dojo-labs ~/owasp/buffer_overflow $ gcc example02.c -oexample02
-ggdb
/tmp/cccbMjcN.o: In function `doit':
/home/user/owasp/buffer_overflow/example02.c:8: warning: the `gets'
function is dangerous and should not be used.
Usage example:
user@dojo-labs ~/owasp/buffer_overflow $ ./example02
So... The End...
TEST // user data on input
TEST // print out stored user data
or... maybe not?
El programa entre las dos llamadas printf() definidas muestra el contenido del búfer, que se llena con los datos ingresados por el usuario.
user@dojo-labs ~/owasp/buffer_overflow $ ./example02
So... The End...
TEST123456789
TEST123456789
Segmentation fault
Debido a que se definió el tamaño del buffer (char buf[8]) y se llenó con trece caracteres de tipo char, el buffer se desbordó. Si nuestra aplicación binaria está en formato ELF, entonces podemos usar un programa objdump para analizarla y encontrar la información necesaria para explotar el error de desbordamiento del búfer. A continuación se muestra el resultado producido por objdump. A partir de esa salida podemos encontrar direcciones, donde se llama a printf() (0x80483d6 y 0x80483e7).
user@dojo-labs ~/owasp/buffer_overflow $ objdump -d./example02
080483be <main>:
80483be: 8d 4c 24 04 lea 0x4(%esp),%ecx
80483c2: 83 e4 f0 and $0xfffffff0,%esp
80483c5: ff 71 fc pushl 0xfffffffc(%ecx)
80483c8: 55 push %ebp
80483c9: 89 e5 mov %esp,%ebp
80483cb: 51 push %ecx
80483cc: 83 ec 04 sub $0x4,%esp
80483cf: c7 04 24 bc 84 04 08 movl $0x80484bc,(%esp) 80483d6: e8 f5 fe ff ff call 80482d0 <puts@plt>
80483db: e8 c0 ff ff ff call 80483a0 <doit>
80483e0: c7 04 24 cd 84 04 08 movl $0x80484cd,(%esp) 80483e7: e8 e4 fe ff ff call 80482d0 <puts@plt>
80483ec: b8 00 00 00 00 mov $0x0,%eax
80483f1: 83 c4 04 add $0x4,%esp
80483f4: 59 pop %ecx
80483f5: 5d pop %ebp
80483f6: 8d 61 fc lea 0xfffffffc(%ecx),%esp
80483f9: c3 ret
80483fa: 90 nop
80483fb: 90 nop
Si la segunda llamada a printf() informaría al administrador sobre el cierre de sesión del usuario (por ejemplo, sesión cerrada), entonces podemos intentar omitir este paso y finalizar sin la llamada a printf().
user@dojo-labs ~/owasp/buffer_overflow $ perl -e'print "A"x12
."\xf9\x83\x04\x08"' | ./example02
So... The End...
AAAAAAAAAAAAu*.
Segmentation fault
La aplicación finalizó su ejecución con fallo de segmentación, pero la segunda llamada a printf() no tuvo cabida. Algunas palabras de explicación:
perl -e ‘print “A”x12 .”\xf9\x83\x04\x08”’ – imprimirá doce caracteres “A” y luego cuatro caracteres, que de hecho son una dirección de la instrucción que queremos ejecutar. ¿Por qué doce?
8 // size of buf (char buf[8])
+ 4 // four additional bytes for overwriting stack frame pointer
—-
12
Análisis del problema:
La cuestión es la misma que en el primer ejemplo. No hay control sobre el tamaño del buffer copiado en el previamente declarado. En este ejemplo, sobrescribimos el registro EIP con la dirección 0x080483f9, que de hecho es una llamada a ret en la última fase de la ejecución del programa.
¿Cómo utilizar los errores de desbordamiento del búfer de una forma diferente?
Generalmente, la explotación de estos errores puede conducir a:
- aplicación DoS
- reordenar la ejecución de funciones
- ejecución de código (si podemos inyectar el código shell, descrito en el documento separado)
¿Cómo se cometen los errores de desbordamiento del búfer?
Este tipo de errores son muy fáciles de cometer. Durante años fueron la pesadilla de un programador. El problema radica en las funciones nativas de C, a las que no les importa realizar comprobaciones adecuadas de la longitud del búfer. A continuación se muestra la lista de dichas funciones y, si existen, sus equivalentes seguros:
- gets() -\> fgets()- leer caracteres
- strcpy() -\> strncpy()- copiar el contenido del buffer
- strcat() -\> strncat()- concatenación de buffer
- sprintf() -\> snprintf()- llenar el búfer con datos de diferentes tipos
- (f)scanf()- leer desde STDIN
- getwd()- volver al directorio de trabajo
- realpath()- devolver la ruta absoluta (completa)
Utilice funciones equivalentes seguras, que verifiquen la longitud de los buffers, siempre que sea posible. A saber:
- gets() -\> fgets()
- strcpy() -\> strncpy()
- strcat() -\> strncat()
- sprintf() -\> snprintf()
Aquellas funciones que no tienen equivalentes seguros deben reescribirse implementando comprobaciones seguras. El tiempo dedicado a ello se beneficiará en el futuro. Recuerda que tienes que hacerlo sólo una vez. Utilice compiladores, que sean capaces de identificar funciones inseguras, errores lógicos y comprobar si la memoria se sobrescribe cuando y donde no debería estar.
Explotaciones de aplicaciones web: cómo los piratas informáticos aprovechan las vulnerabilidades de desbordamiento del búfer
testfire.net
En el navegador web Firefox , busque la siguiente URL:
Esta es una aplicación web deliberadamente vulnerable que se utiliza con fines educativos y de prueba. Seleccione Iniciar sesión e inicie sesión en la aplicación utilizando las siguientes credenciales:
Nombre de usuario → admin
Contraseña → admin
Haga clic en el enlace Feedback junto a la barra de búsqueda en la esquina superior derecha.
Complete el formulario con la siguiente información:
Nombre: Laprovittera
Dirección de correo electrónico: carlos@laprovittera.com
Suject: test
Pregunta/Comentario:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
En el campo Pregunta/Comentario , proporcione un gran volumen de datos aleatorios.
Haga clic en Enviar .
La aplicación no realiza una verificación de longitud y es vulnerable a ataques de desbordamiento.
Si un atacante envía más datos de los que el búfer puede contener, el exceso de datos se desbordará hacia áreas de memoria adyacentes, sobrescribiendo potencialmente datos críticos o el código de la aplicación. Esto puede provocar un comportamiento impredecible y potencialmente malicioso.
No te detengas, sigue avanzando
Aquí tienes un propósito para este 2024 que debes considerar seriamente: si has querido mejorar tus habilidades en hacking, Ciberseguridad y programación ahora es definitivamente el momento de dar el siguiente paso. ¡Desarrolla tus habilidades aprovechando nuestros cursos a un precio increíble y avanza en tu carrera!
Pentesting Web. Prácticas de un Ethical Hacker Profesional
Aprende Pentesting Web, Hacking Ético y Ciberseguridad. Practicas reales y aprender todo sobre Vulnerar entornos Web.
Calificación: 4,2 de 54,2 (609 calificaciones) 30.473 estudiantes Creado por Alvaro Chirou • 1.800.000+ Enrollments Worldwide, Walter Coto || +440,000 Estudiantes
Lo que aprenderás
- Hacking Ético Web
- El TOP 10 Vulnerabilidades Web de OWASP
- Uso de Burp Suite
- Identificar Vulnerabilidades Web
- Explotar Vulnerabilidades Web
- Identificar Vulnerabilidades que NO son de OWASP TOP 10
- Prácticar con Escenarios Reales de Hacking Web
- Las 5 Fases del Pentesting Web
El Pentesting Web se enfoca principalmente en aplicaciones y paginas funcionando en internet. Esto genera que su demanda laboral sea además de muy alta, sumamente importante.
La gran ventaja de esta profesión es que efectivamente, puedes llevarla adelante desde cualquier parte del mundo, con una computadora e internet.
Nosotros te brindaremos los fundamentos teóricos y la base necesaria para que empieces desde 0, y vayas avanzando hacia el ámbito profesional, con laboratorios simulando entornos reales
Hemos creado esta formación profesional donde aprenderás todo lo que necesitas para ser un experto, y con practicas que podrás aplicar en escenarios reales.
Contaras con nuestra supervisión, experiencia y respuesta a todas tus preguntas que tengas sobre el contenido. Así también sobre las actualizaciones que hagamos sobre el curso, el cual será tuyo de por vida y recibirás dichas actualizaciones sin tener que volver a pagar.
Empieza a aprender ya mismo y acompáñame en este increíble curso.
¿Te gustaría enterarte de cuando lanzamos descuentos y nuevos cursos?
Sobre los autores
Álvaro Chirou
Yo soy Álvaro Chirou, tengo más de 20 Años de experiencia trabajando en Tecnología, eh dado disertaciones en eventos internacionales como OWASP, tengo más de 1.800.000 estudiantes en Udemy y 100 formaciones profesionales impartidas en la misma. Puedes serguirme en mis redes:
Laprovittera Carlos
Soy Laprovittera Carlos. Con más de 20 años de experiencia en IT brindo Educación y Consultoría en Seguridad de la Información para profesionales, bancos y empresas. Puedes saber más de mi y de mis servicios en mi sitio web: laprovittera.com y seguirme en mis redes:
¿Quieres iniciarte en hacking y ciberseguridad pero no sabes por dónde empezar? Inicia leyendo nuestra guia gratuita: https://achirou.com/como-iniciarse-en-ciberseguridad-y-hacking-en-2024/ que te lleva de 0 a 100. Desde los fundamentos más básicos, pasando por cursos, recursos y certificaciones hasta cómo obtener tu primer empleo.