2010-09-07 10 views
17

OWASP dice:la comprensión de los peligros de sprintf (...)

"funciones de biblioteca C, tales como strcpy (), strcat(), sprintf() y vsprintf () operan en cadenas terminada en nulo y no realiza controles de límites ".

sprintf escribe datos en cadena int sprintf formateados (char * str, const char * formato, ...);

Ejemplo:

sprintf(str, "%s", message); // assume declaration and 
          // initialization of variables 

Si entiendo el comentario de OWASP, a continuación, los peligros del uso de sprintf son que

1) si mensaje 's length>str' s de longitud, hay una desbordamiento del búfer

y

2) si mensaje no nulo terminar con \0, entonces mensaje podría conseguir copiado en str más allá de la dirección de memoria de mensaje, provocando un desbordamiento de búfer

Por favor confirmar/negar. Gracias

+2

Además, el error del programador como 'sprintf (str, message)' o similar es un riesgo real. – You

Respuesta

20

Tiene razón en ambos problemas, aunque en realidad son ambos el mismo problema (que es acceder a los datos más allá de los límites de una matriz).

Una solución a su primer problema es utilizar en su lugar snprintf, que acepta un tamaño de búfer como argumento.

Una solución a su segundo problema es dar un argumento de longitud máxima a s[n]printf. Por ejemplo:

char buffer[128]; 

snprintf(buffer, sizeof(buffer), "This is a %.4s\n", "testGARBAGE DATA"); 

// strcmp(buffer, "This is a test\n") == 0 

Si desea almacenar toda la cadena (por ejemplo, en el caso sizeof(buffer) es demasiado pequeño), ejecute snprintf dos veces:

// Behaviour is different in SUSv2; see 
// "conforming to" section of man 3 sprintf 

int length = snprintf(NULL, 0, "This is a %.4s\n", "testGARBAGE DATA"); 

++length;   // +1 for null terminator 
char *buffer = malloc(length); 

snprintf(buffer, length, "This is a %.4s\n", "testGARBAGE DATA"); 

(es probable que pueda encajar esto en una función utilizando va.)

+4

¿Cómo son "ambos el mismo problema" cuando 'snprintf' solo resuelve el primero? –

+0

@Rob Kennedy, Son el mismo problema en diferentes lugares. Lo siento, no estaba claro. Tienes razón; 'snprintf' reemplazar ciegamente' sprintf' solo corrige el primero. – strager

+0

snprintf también truncará los argumentos pasados, resolviendo el número 2 (si el mensaje no tiene terminación NULL, solo se escribirán los primeros N caracteres). –

1

Su interpretación parece ser la correcta. Sin embargo, su caso n. ° 2 no es realmente un desbordamiento de búfer. Es más una violación de acceso a la memoria. Sin embargo, eso solo es terminología, sigue siendo un problema importante.

4

Sí, es principalmente una cuestión de desbordamientos de búfer. Sin embargo, ahora son asuntos bastante serios, ya que los desbordamientos de los búferes son el principal vector de ataque utilizado por los crackers del sistema para eludir la seguridad del software o del sistema. Si expone algo como esto a la entrada del usuario, hay muchas posibilidades de que entregue las llaves de su programa (o incluso su propia computadora) a los crackers.

Desde la perspectiva de OWASP, pretendemos que estamos escribiendo un servidor web, y usamos sprintf para analizar la entrada que un navegador nos pasa.

Supongamos ahora que alguien malintencionado pasa nuestro navegador web una cadena mucho más grande que la que cabe en el búfer que elegimos. Sus datos adicionales en su lugar sobrescribirán datos cercanos. Si lo hace lo suficientemente grande, algunos de sus datos se copiarán a través de las instrucciones del servidor web en lugar de sus datos. Ahora puede hacer que nuestro servidor web ejecute su código.

8

Ambas aserciones son correctas.

Hay un problema adicional no mencionado. No hay verificación de tipo en los parámetros. Si no coincide con la cadena de formato y los parámetros, podría producirse un comportamiento indefinido y no deseado. Por ejemplo:

char buf[1024] = {0}; 
float f = 42.0f; 
sprintf(buf, "%s", f); // `f` isn't a string. the sun may explode here 

Esto puede ser especialmente desagradable de depurar.

Todo lo anterior llevó a muchos desarrolladores de C++ a la conclusión de que nunca debe usar sprintf y sus hermanos. De hecho, hay instalaciones que puede utilizar para evitar todos los problemas anteriores. Uno, corrientes, está construido justo en el idioma:

#include <sstream> 
#include <string> 

// ... 

float f = 42.0f; 

stringstream ss; 
ss << f; 
string s = ss.str(); 

... y otra opción popular para aquellos que, como yo, todavía prefieren utilizar sprintf proviene de la boost Format libraries:

#include <string> 
#include <boost\format.hpp> 

// ... 

float f = 42.0f; 
string s = (boost::format("%1%") %f).str(); 

¿Debes adoptar el mantra "nunca usar sprintf"? Decide por ti mismo. Usualmente hay una mejor herramienta para el trabajo y, dependiendo de lo que esté haciendo, puede que sea sprintf.

+1

GCC (y creo que muchos otros) realizan una verificación de tipo suave. Además, el separador de ruta en '# include' es'/'sin importar qué plataforma. – Potatoswatter

+0

@Potatoswatter: la interpretación de cualquier carácter (incluidos '\' y '/') en '# include' se deja a la implementación. El estándar ni siquiera afirma que hay un sistema de archivos, y mucho menos rutas con separadores de rutas. – MSalters

+0

@MSalters: Sí, pero básicamente cada implementación que admita nombres de ruta admitirá '/' la compatibilidad con el código y los libros de texto existentes, y no tanto con '\'. Quizás más importante aún, tener '\ u' allí puede producir un nombre de carácter universal. Para el caso, una plataforma puede decidir muy razonablemente sustituir secuencias de escape en lugar de o además de usarlas como separador. Entonces, ¿cuál es la ventaja? – Potatoswatter

1

La función sprintf, cuando se usa con ciertos especificadores de formato, presenta dos tipos de riesgos de seguridad: (1) escribir memoria no debería; (2) leer memoria no debería. Si snprintf se usa con un parámetro de tamaño que coincida con el búfer, no escribirá nada que no debería. Dependiendo de los parámetros, todavía puede leer cosas que no debería. Dependiendo del entorno operativo y de qué más esté haciendo un programa, el peligro de lecturas incorrectas puede ser o no ser menos severo que el de las escrituras incorrectas.

2

Sus 2 conclusiones numeradas son correctas, pero incompletas.

Hay un riesgo adicional:

char* format = 0; 
char buf[128]; 
sprintf(buf, format, "hello"); 

Aquí, format no es terminada en NULL. sprintf() no comprueba eso tampoco.

0

Es muy importante recordar que sprintf() agrega el carácter ASCII 0 como terminador de cadena al final de cada cadena. Por lo tanto, el búfer de destino debe tener al menos n + 1 bytes (para imprimir la palabra "HOLA", se requiere un búfer de 6 bytes, NO 5)

En el ejemplo siguiente, puede no ser obvio, pero en el búfer de destino de 2 bytes, el segundo byte se sobrescribirá con el carácter ASCII 0. Si solo se asignó 1 byte para el búfer, esto causaría el desbordamiento del búfer.

char buf[3] = {'1', '2'}; 
int n = sprintf(buf, "A"); 

También tenga en cuenta que el valor de retorno de sprintf() NO incluye el carácter de terminación nula. En el ejemplo anterior, se escribieron 2 bytes, pero la función devuelve '1'.

En el ejemplo siguiente, la variable miembro 'by' del primer byte de la clase se sobrescribirá parcialmente con sprintf() (en un sistema de 32 bits).

struct S 
{ 
    char buf[4]; 
    int i; 
}; 


int main() 
{ 
    struct S s = { }; 
    s.i = 12345; 

    int num = sprintf(s.buf, "ABCD"); 
    // The value of s.i is NOT 12345 anymore ! 

    return 0; 
} 
Cuestiones relacionadas