Reino: Input Validation and Representation

Los problemas de validación y representación de entradas están causados por metacaracteres, codificaciones alternativas y representaciones numéricas. Los problemas de seguridad surgen de entradas en las que se confía. Estos problemas incluyen: «desbordamientos de búfer», ataques de «scripts de sitios», "SQL injection" y muchas otras acciones.

Buffer Overflow

Abstract
Al escribir fuera de los límites de un bloque de memoria asignada, es posible que se dañen los datos, se bloquee el programa o se provoque la ejecución de código malintencionado.
Explanation
El buffer overflow es probablemente la forma más conocida de vulnerabilidad de seguridad de software. La mayoría de los desarrolladores de software saben lo que es una vulnerabilidad de buffer overflow, pero a menudo este tipo de ataques contra las aplicaciones existentes y desarrolladas recientemente son aún bastante habituales. Parte del problema se debe a la amplia variedad de formas en las que puede producirse un buffer overflow y otra parte se debe a las técnicas proclives a errores que a menudo se utilizan para evitarlas.

En un ataque de buffer overflow clásico, el usuario malintencionado envía datos a un programa, que los almacena en un búfer de pila demasiado pequeño. El resultado es que se sobrescribe la información de la pila de llamadas, incluido el puntero de devolución de la función. Los datos establecen el valor del puntero de devolución para que, cuando se devuelva la función, esta transfiera el control al código malicioso incluido en los datos del usuario malintencionado.

Aunque este tipo de buffer overflow de pila aún es frecuente en algunas plataformas y comunidades de desarrolladores, existen diversos tipos adicionales de buffer overflow, incluidos los desbordamientos del búfer de montón y los errores por uno ("off-by-one"), entre otros. Hay una serie de libros excelentes que ofrecen información detallada sobre cómo funcionan los ataques de buffer overflow, incluidos "Bilding Secure Software" [1], "Writing Secure Code" [2] y "The Shellcoder's Handbook" [3].

En el nivel de código, las vulnerabilidades de buffer overflow normalmente conllevan la infracción de las presuposiciones de un programador. Muchas funciones de manipulación de memoria de C y C++ no realizan comprobaciones de límites y pueden sobrescribir fácilmente los límites asignados de los búferes en los que funcionan. Incluso las funciones limitadas como, por ejemplo, strncpy(), pueden provocar vulnerabilidades cuando se utilizan incorrectamente. La combinación de manipulación de memoria y presuposiciones erróneas acerca del tamaño y la formación de una unidad de datos es el motivo principal de la mayoría de desbordamientos del búfer.

Las vulnerabilidades de buffer overflow suelen producirse en código que:

- Utiliza datos externos para controlar su comportamiento.

- Depende de las propiedades de los datos que se aplican fuera del ámbito inmediato del código.

- Es tan complejo que un programador no puede predecir con precisión su comportamiento.



Los siguientes ejemplos muestran estos tres escenarios.

Ejemplo 1.a: el siguiente código de ejemplo muestra un buffer overflow sencillo que a menudo lo provoca el primer escenario en el que el código utiliza los datos externos para controlar su comportamiento. El código utiliza la función gets() para leer una cantidad arbitraria de datos en un búfer de pila. Como no hay forma de limitar la cantidad de datos leídos por esta función, la seguridad del código depende siempre de que el usuario introduzca un número de caracteres inferior a BUFSIZE.


...
char buf[BUFSIZE];
gets(buf);
...
Ejemplo 1.b: en este ejemplo se muestra lo fácil que es imitar el comportamiento poco seguro de la función gets() en C++ mediante el uso del operador >> para leer la entrada en una cadena char[].


...
char buf[BUFSIZE];
cin >> (buf);
...
Ejemplo 2: el código de este ejemplo utiliza también la entrada de usuario para controlar su comportamiento, pero añade un nivel de indirección con el uso de la función de copia de memoria limitada memcpy(). Esta función acepta un búfer de destino y uno de origen, y el número de bytes que se va a copiar. El búfer de entrada se llena con una llamada limitada a read(). Sin embargo, el usuario especifica el número de bytes que memcpy() copia.


...
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 buffer overflow (en el que un programa lee datos y, a continuación, confía en un valor de los datos de las operaciones de memoria posteriores en los datos restantes) ha surgido con frecuencia en bibliotecas 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 han verificado localmente. En este ejemplo una función denominada lccopy() utiliza una cadena como argumento y devuelve la copia asignada por montón de la cadena con las letras en mayúsculas convertidas a minúsculas. La función no realiza ninguna comprobación de límites en esta entrada por esquema que str sea siempre menor que BUFSIZE. Si un usuario malintencionado omite las comprobaciones del código que llama a lccopy() o si un cambio realizado en ese código invalida la presuposición acerca del tamaño de str, lccopy() desbordará buf con la llamada a strcpy() no limitada.


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.

El código parece realizar con seguridad la comprobación de límites porque comprueba el tamaño de la longitud de variable, que se utiliza posteriormente para calcular la cantidad de datos copiados por png_crc_read(). Sin embargo, justo después de que se pruebe la longitud, el código realiza una comprobación en png_ptr->mode y, si esta presenta errores, se emite una advertencia y el proceso continúa. Como length se prueba en un bloque else if, length no se probará si la primera comprobación presenta errores y se utilizará ciegamente en la llamada a png_crc_read(), provocando un posible buffer overflow de pila.

Aunque el código de este ejemplo no es el más complejo que hayamos visto, muestra por qué debe reducirse al mínimo 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: en este ejemplo también se muestra el tercer escenario en el que la complejidad del programa lo expone a desbordamientos del búfer. En ese caso, la exposición se debe a la interfaz ambigua de una de las funciones en lugar de a la estructura del código (como sí lo hacía en el ejemplo anterior).

La función getUserInfo() utiliza un nombre de usuario especificado por una cadena multibyte y un puntero a una estructura para la información de usuario, y rellena la estructura con información del usuario. Como la autenticación de Windows utiliza Unicode para los nombres de usuario, el argumento username se convierte primero de una cadena multibyte a una Unicode. A continuación, esta función transfiere de forma incorrecta el tamaño de unicodeUser en bytes en lugar de caracteres. Así pues, 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 username contiene más de UNLEN caracteres, 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);
}
References
[1] J. Viega, G. McGraw Building Secure Software Addison-Wesley
[2] M. Howard, D. LeBlanc Writing Secure Code, Second Edition Microsoft Press
[3] J. Koziol et al. The Shellcoder's Handbook: Discovering and Exploiting Security Holes John Wiley & Sons
[4] About Strsafe.h Microsoft
desc.dataflow.cpp.buffer_overflow