Vulnerabilidades en el software Facultad Politécnica – UNA Maestría en TICs – 2015 Énfasis Auditoría y Seguridad de la Información Seguridad en aplicaciones y base de datos Cristian Cappo (ccappo@pol.una.py) NIDTEC - Núcleo de Investigación y Desarrollo Tecnológico - FPUNA Contenido Los 25 errores más peligrosos en software (Según SANS - Instituto en US especializado en seguridad de la información y ciberseguridad – SysAdmin-Audit-Networking-Security) Breve introducción a lenguaje C (en diapositiva separada) Buffer Overflow. Descripción. Defensas Prácticas para desarrollo de software seguro 2 25 top errores de SW más peligrosos Categoría: Interacción insegura entre componentes Error al preservar la estructura de la página web(XSS) Error al preservar la estructura de la consulta SQL Neutralización impropia en elementos especial usados en comandos del S.O. (OS command injection) Cross Site Request Forgery (CSRF) Subida de archivos peligrosos no restringido Redirección de URL a sitios no confiables (Open Redirect). http://example.com/example.php?url=http://malicious.example.co m 3 25 top errores de SW más peligrosos(2) Categoría: Manejo riesgoso de recursos Copia de buffer sin verificar el tamaño (buffer overflow) Limitación impropia de un camino a un directorio restringuido (Path traversal) Descarga de código sin chequeo de integridad Inclusión de funcionalidad desde lugares no confiables Uso de funciones potencialmente peligrosas gets(), strcpy(), strcat(), etc Acceso a buffers con longitudes de tamaño incorrectas Cálculo incorrecto del tamaño del buffer Formato de cadenas no controlado Integer overflow 4 25 top errores de SW más peligrosos(3) Categoría: Defensa porosa o insuficiente Olvido de autenticación en funciones críticas Falta de autorización Uso de credenciales “fijas” Olvido de encriptación en datos sensibles Confianza en entradas no verificadas en una decisión de seguridad Ejecución con privilegios innecesarios Control de acceso indebido (autorización) Uso de un algoritmo criptográfico inseguro o “roto” Restricción impropia a intentos excesivos de autenticación. Uso de Hash de una dirección sin SALT 5 Seguridad en el software Muchas vulnerabilidades resultan de una práctica de programación deficiente Consecuencia de insuficientes validaciones y chequeos de datos y códigos de error Calidad/Confiabilidad en el SW Concerniente con la falla accidental del programa como resultado de aleatoriedad, entrada no anticipada, interacción con el SO o uso de código incorrecto. Se mejora usando técnicas de diseño y testing para eliminar los bugs. Seguridad en el SW: Existe un atacante que utiliza entradas totalmente diferentes a las esperadas. Puede que no se detecten con las técnicas de testing usuales. 6 Bug & Exploit Un bug es un lugar donde el comportamiento de la ejecución real puede desviarse del comportamiento esperado. Un exploit es una entrada que dá al atacante una ventaja Método Objetivo Secuestro del flujo de control Ganar el control del puntero de instrucción %eip (Control Flow Hijack) Denegación de Servicio Causar que el programa se interrumpa o pare de dar servicio Descubierta de Informacion Fuga de información privada ej., contraseñas guardadas 7 Buffer overflow Una condición que ocurre cuando datos son escritos fuera del espacio destinado al buffer que debe contenerlos. Usualmente con lenguajes de bajo nivel como C y C++. Un programa normal con este bug simplemente aborta y se cancela. Consecuencias: Corrupción de los datos del programa, transferencia inesperada del control de ejecución, violación de acceso a memoria, ejecución de código del atacante. Tipos Basados en Stack Basados en Heap 8 Buffer overflow - historia Año Evento 1988 Morris (Robert) Internet Worm. Usó un bof para comprometer una versión vulnerable de fingerd en máquinas VAX. Enviaba una cadena especial al demonio finger, el cual causaba la ejecución de código que creaba una nueva copia. 10% de hosts afectados (del Internet de esa época) 1995 BOF en httpd 1.3 de NCSA descubierto y publicado en bugtraq por Thomas Lopatic 1996 Aleph One publicó “Smashing the stack for fun & profit” en el magazine Phrack, dando los pasos para explotar los BOF basados en stack. 2001 CodeRed (motivó la carta de Bill Gates para crear una plataforma de sistemas seguros). Explotó un BOF en el servidor MS IIS 5.0. 300000 máquinas afectadas en 14 horas 2003 SQL Slammer (worm). Explotó un BOF en MS SQL Server 2000. 75000 máquinas afectadas en 10 minutos. 2004 Sasser worm. Explotó un BOF en MS Window 2000/XP en el servicio de subsistema de seguridad de autoridad local (Local Security Authority Subsytem service) 2014 BOF descubierto en código del servidor X11 server que estuvo latente en el fuente por 20 años!! 9 Buffer overflow – tendencia https://web.nvd.nist.gov/view/vuln/statistics-results?adv_search=true&cves=on&cwe_id=CWE-119 10 Secuestro de flujo de control Siempre Cómputo + Control shellcode (payload) Cómputo • • • • • relleno + Inyección de código Return-to-libc Sobreescrita del Heap Return-oriented programming ... &buf control El mismo principio pero diferentes mecanismos 11 Layout de memoria 12 Stack frame con funciones P y Q 13 Memory layout para Intel x86 0xFFFFFFFF 4GB Línea de comandos & entorno STACK int f() {int x; ..} + Metadata HEAP malloc(sizeof(long)) Tiempo de ejecución Datos No inicializados Tiempo de compilación Datos Inicializados Text segment (código) 0x00000000 static int x; static const int y = 10; 0x4c2 0x4c1 0x4bf 0x4be .. sub $0x224, %esp push %ecx mov %esp, %ebp push %ebp 14 Ejecución básica Fetch, decode, execute Binario Código Datos ... Procesador Stack Heap File system Lectura & Escritura Memoria del proceso 15 Instrucciones asm x86 usuales 16 Registros usuales en x86 32 bit 16 bit 8 bit 8 bit Uso %eax %ax %ah %al Acumuladores usados para operaciones aritméticas y operaciones de I/O y ejecutar llamadas de interrupción %ebx %bx %bh %bl Acceso a memoria, paso de argumentos a llamadas del sistema y retorno de valores %ecx %cx %ch %cl Registros de contadores %edx %dx %dh %dl Registro de datos para operaciones aritméticas, llamadas de interrupción y operaciones de I/O %ebp Puntero base que contiene la dirección del actual stack frame %eip Puntero de instrucciones o contador de programa que contiene la dirección de la siguiente instrucción a ser ejecutada %esi Registro de fuente índice de fuente usada como puntero para operaciones de arreglos o cadenas %esp Puntero de Pila que contiene la dirección del tope de la pila 17 cdecl – Por defecto para Linux & gcc (orden de colocación de parámetros en la pila) b a return addr caller’s ebp locals (buf, c, d ≥ 28 bytes if stored on stack) buf c return addr orange’s ebp … %ebp frame %esp stack crecimiento int orange(int a, int b) Parametros { (llamador) char buf[16]; int c, d; Stack inicial if(a > b) de orange c = a; else c = b; Creado antes d = red(c, buf); de llamar a red return d; } Luego que red haya sido llamado … 18 Proceso de llamada a una función Función llamadora Colocar argumentos en la pila (en orden reverso) Colocar el return address, es decir, la dirección de la instrucción que quieres que se ejecute luego que el control te retorne Saltar a la dirección de la función Función llamada Colocar el %ebp antiguo en la pila Hacer que %ebp sea ahora %esp Push de las variables locales en la Pila Retornando a la función Reasignar el stack frame previo %esp=%ebp ó %ebp=(%ebp) Saltar a la dirección de retorno (return address) %eip = 4(%esp) 19 Buffer overflow ¿Qué pasa cuando escribimos fuera del buffer? Según el estándar el programa queda indefinido. void func(char * arg1 ) { char buffer[4]; strcpy(buffer, arg1); .. } int main() { char * msgstr = “Authme!”; func(msgstr); } A u t h m e ! %ebp Buffer \0 %eip &arg1 Crash !! (segmentation fault) 20 void func(char * arg1 ) { int authenticated = 0; char buffer[4]; strcpy(buffer, arg1); if ( authenticated ) .. .. } int main() { char * msgstr = “Authme!”; func(msgstr); } A u buffer t h m e ! \0 4d 21 00 65 authenticated %ebp &eip &arg1 Pasa la validación!! 21 Ejemplo básico de vulnerabilidad #include <string.h> int main(int argc, char **argv) { char buf[64]; strcpy(buf, argv[1]); } Desensamblado 0x080483e4 0x080483e5 0x080483e7 0x080483ea 0x080483ed 0x080483f0 0x080483f4 0x080483f7 0x080483fa 0x080483ff 0x08048400 de la función main: <+0>: push %ebp <+1>: mov %esp,%ebp <+3>: sub $72,%esp <+6>: mov 12(%ebp),%eax <+9>: mov 4(%eax),%eax <+12>: mov %eax,4(%esp) <+16>: lea -64(%ebp),%eax <+19>: mov %eax,(%esp) <+22>: call 0x8048300 <strcpy@plt> <+27>: leave <+28>: ret … argv argc return addr caller’s ebp buf (64 bytes) %ebp argv[1] buf %esp 22 “123456” Desensamblado 0x080483e4 0x080483e5 0x080483e7 0x080483ea 0x080483ed 0x080483f0 0x080483f4 0x080483f7 0x080483fa 0x080483ff 0x08048400 de la función main: <+0>: push %ebp <+1>: mov %esp,%ebp <+3>: sub $72,%esp <+6>: mov 12(%ebp),%eax <+9>: mov 4(%eax),%eax <+12>: mov %eax,4(%esp) <+16>: lea -64(%ebp),%eax <+19>: mov %eax,(%esp) <+22>: call 0x8048300 <strcpy@plt> <+27>: leave <+28>: ret … argv argc return addr caller’s ebp buf (64 bytes) %ebp 123456\0 #include <string.h> int main(int argc, char **argv) { char buf[64]; strcpy(buf, argv[1]); } argv[1] buf %esp 23 “A”x68 . “\xEF\xBE\xAD\xDE” Desensamblado 0x080483e4 0x080483e5 0x080483e7 0x080483ea 0x080483ed 0x080483f0 0x080483f4 0x080483f7 0x080483fa 0x080483ff 0x08048400 … argv argc corrompido return addr sobreescrito 0xDEADBEEF AAAAebp sobreescrito caller’s de la función main: <+0>: push %ebp <+1>: mov %esp,%ebp <+3>: sub $72,%esp <+6>: mov 12(%ebp),%eax <+9>: mov 4(%eax),%eax <+12>: mov %eax,4(%esp) <+16>: lea -64(%ebp),%eax <+19>: mov %eax,(%esp) <+22>: call 0x8048300 <strcpy@plt> <+27>: leave <+28>: ret buf (64 bytes) %ebp AAAA… (64 in total) #include <string.h> int main(int argc, char **argv) { char buf[64]; strcpy(buf, argv[1]); } argv[1] buf %esp 24 Desmontando el Frame - 1 #include <string.h> int main(int argc, char **argv) { char buf[64]; strcpy(buf, argv[1]); } Desensamblado 0x080483e4 0x080483e5 0x080483e7 0x080483ea 0x080483ed 0x080483f0 0x080483f4 0x080483f7 0x080483fa => 0x080483ff 0x08048400 … argv argc corrompido sobrescrito 0xDEADBEEF AAAA sobrescrito de la función main: <+0>: push %ebp <+1>: mov %esp,%ebp leave <+3>: sub $72,%esp 1. mov <+6>: mov 12(%ebp),%eax <+9>: mov 4(%eax),%eax 2. pop <+12>: mov %eax,4(%esp) <+16>: lea -64(%ebp),%eax <+19>: mov %eax,(%esp) <+22>: call 0x8048300 <strcpy@plt> <+27>: leave <+28>: ret %esp y %ebp %ebp,%esp %ebp %esp 25 Desmontando el Frame - 2 #include <string.h> int main(int argc, char **argv) { char buf[64]; strcpy(buf, argv[1]); } Desensamblado 0x080483e4 0x080483e5 0x080483e7 0x080483ea 0x080483ed 0x080483f0 0x080483f4 0x080483f7 0x080483fa 0x080483ff 0x08048400 … argv argc corrompido sobrescrito 0xDEADBEEF de la función main: %ebp = AAAA <+0>: push %ebp <+1>: mov %esp,%ebp leave <+3>: sub $72,%esp 1. mov %ebp,%esp <+6>: mov 12(%ebp),%eax <+9>: mov 4(%eax),%eax 2. pop %ebp <+12>: mov %eax,4(%esp) <+16>: lea -64(%ebp),%eax <+19>: mov %eax,(%esp) <+22>: call 0x8048300 <strcpy@plt> <+27>: leave <+28>: ret %esp 26 Desmontando el Frame - 3 #include <string.h> int main(int argc, char **argv) { char buf[64]; strcpy(buf, argv[1]); } Desensamblado 0x080483e4 0x080483e5 0x080483e7 0x080483ea 0x080483ed 0x080483f0 0x080483f4 0x080483f7 0x080483fa 0x080483ff 0x08048400 … argv corrompido argc %esp de la función main: <+0>: push %ebp <+1>: mov %esp,%ebp %eip = 0xDEADBEEF <+3>: sub $72,%esp (probablemente crash) <+6>: mov 12(%ebp),%eax <+9>: mov 4(%eax),%eax <+12>: mov %eax,4(%esp) <+16>: lea -64(%ebp),%eax <+19>: mov %eax,(%esp) <+22>: call 0x8048300 <strcpy@plt> <+27>: leave <+28>: ret 27 Inyección de código En el ejemplo anterior dimos una cadena estática pero éstas pueden venir de: Entrada de texto Variables de entorno Paquetes (red) Archivos 28 Inyección de código Desafíos Leer mi código en memoria Código de máquina Cuidado para construir (no debe tener \0) ¿Qué código? Un intérprete de línea de comandos (shell) #include <string.h> int main() { char * name[2]; name[0] = “/bin/sh”; name[1] = NULL; execve(name[0],name,NULL); } 29 Shellcode Tradicionalmente, inyectamos código ensamblador para exec(“/bin/sh”) en el buffer. … argv argc &buf … 0x080483fa <+22>: call 0x080483ff <+27>: leave 0x08048400 <+28>: ret %ebp shellcode… … 0x8048300 <strcpy@plt> argv[1] buf %esp 30 Ejecutando una llamada al sistema •execve(“/bin/sh”, 0, 0);en eax 1. Colocar la llamada al sistema 2. arg 1 en %ebx, arg 2 en %ecx, arg 3 execve es en %edx 0xb 3. llamar int 0x80* addr. en 4. Llamada al sistema %ebx, con resultado en %eax 0 en %ecx 31 Ejemplo de programa #include <stdio.h> #include <string.h> char code[] = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f" "\x73\x68\x68\x2f\x62\x69\x6e\x89" "\xe3\xb0\x0b\xcd\x80"; int main(int argc, char **argv) { printf ("Shellcode length : %d bytes\n", strlen (code)); int(*f)()=(int(*)())code; f(); $ gcc -o shellcode -fno-stack-protector } -z execstack shellcode.c Autor: kernel_panik, http://www.shell-storm.org/shellcode/files/shellcode-752.php 32 Ejecución xor ecx, ecx mul ecx push ecx push 0x68732f2f push 0x6e69622f mov ebx, esp mov al, 0xb int 0x80 Shellcode ebx ecx esp 0 eax 0x0b Registros esp 0x0 0x0 0x68 0x73 0x2f 0x2f 0x6e 0x69 0x62 0x2f h s / / n i b / Autor: kernel_panik, http://www.shell-storm.org/shellcode/files/shellcode-752.php 33 ¿Cómo conozco la dirección? Agregar la instrucción nop y mejoramos nuestra chance. Override %eip %ebp 0xbff nop nop nop \0x31\0xc9\0xf7.. shellcode buffer %eip 34 34 Resumen Para generar un exploit para un buffer overflow básico: Determinar el tamaño del stack frame sobre la cabeza del buffer Inundar el buffer con el tamaño correcto &buf padding cómputo shellcode + control 35 Otros ataques de memoria El que vimos se llama stack smashing (overflow) Constituye una violación de la integridad y también de la disponibilidad Otros tipos Format String Heap overflow Integer overflow Read overflow Stale memory 36 Ataques de cadenas de formato (format strings) Incorrecto OK int wrong(char *user) { printf(user); } int ok(char *user) { printf(“%s”, user); } No abusar de printf 37 Funciones de formateo de cadenas printf(char *fmt, ...) especifica número y tipo de argumentos Número variable de argumentos Función Propósito printf Imprime en stdout fprintf Imprime en un flujo FILE sprintf Imprime en una cadena vfprintf Imprime en un flujo FILE desde va_list syslog Escribe mensajes a system log setproctitle sets argv[0] 38 Funciones con parámetros variables Son aquellas con aridad indefinida Soportado en muchos lenguajes C C++ Javascript Perl PHP En cdecl, el llamador es responsable de limpiar los argumentos ¿Porqué? 39 Ejemplo simple Queremos implementar una función al estilo printf que solo imprime cuando la depuración esta activada void debug(char *key, char *fmt, ...) { va_list ap; char buf[BUFSIZE]; if (!KeyInList(key)) return; va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap); printf(“%s”, buf); } ap es el puntero al argumento Hacer que ap apunte al stack usando el último argumento fijo Llamar a vsprintf con args Limpiamos 40 Diagrama del stack para printf … caller arg n … arg 2 arg 1 return addr caller’s ebp callee-save callee n-1mo argumento locals 1er argmento (va_list) Especificador de formato Va_list es un puntero al segundo argumento Cada formato especifica el tipo del actual argumento Conocido para hacer el salto al siguiente argumento 41 Ejemplo … caller arg 4 arg 3 arg 2 arg 1 return addr caller’s ebp printf callee-save locals 42 Dirección de “world” Dirección de “hello” char s1[] = “hello”; char s2[] = “world”; printf(“%s %s %u”, s1, s2, 42); Dirección de “%s %s %u” 42 1. 2. 3. 4. 5. 080483d4 <foo>: 80483d4: 80483d5: 80483d7: 80483da: 80483dd: 80483e1: 80483e4: 80483e7: 80483ec: 80483ef: 80483f2: 80483f7: 80483f8: push mov sub mov mov lea mov call lea mov call leave ret int foo(char *fmt) { char buf[32]; strcpy(buf, fmt); printf(buf); } %ebp %esp,%ebp $0x28,%esp ; allocate 32 bytes on stack 0x8(%ebp),%eax ; eax := M[ebp+8] - addr of %eax,0x4(%esp) ; M[esp+4] := eax - push as -0x20(%ebp),%eax ; eax := ebp-32 - addr of %eax,(%esp) ; M[esp] := eax - push as 80482fc <strcpy@plt> -0x20(%ebp),%eax ; eax := ebp-32 - addr of %eax,(%esp) ; M[esp] := eax - push as 804830c <printf@plt> fmt arg 2 buf arg 1 buf again arg 1 43 Mirando las direcciones return addr foo caller’s ebp buf (32 bytes) stale arg 2 arg 1 printf return addr foo’s ebp locals 1. 2. 3. => 5. int foo(char *fmt) { char buf[32]; strcpy(buf, fmt); printf(buf); } Note: buf esta abajo de printf en el stack, por tanto podemos caminar a el con el especificador correcto Que pasa si fmt es “%x%s”? 44 Overflow por el formateo de cadena return addr caller’s ebp char buf[32]; sprintf(buf, user); foo Sobreescribir la dirección de retorno buf (32 bytes) “%36u\x3c\xd3\xff\xbf<nops><shellcode>” arg 2 sprintf arg 1 return addr foo’s ebp locals Escribir 36 digitos decimales sobreescribiendo buf y el ebp del llamador Shellcode con nop(no operation) 45 Resumen de ataque por formato de cadena Usar un especificador de formato a fin de “caminar” por el stack hasta conseguir el formato adecuado. Usar el especificador para codificar la dirección Dos formas de sobreescribir: Usando %n (número de bytes escritos hasta aquí y lo deja en un parámetro) Usando sprintf para un overflow básico 46 Heap overflow Localizado usualmente bajo el código del programa. Relacionado al uso de estructura de datos dinámica No existe dirección de retorno Defensas: Hacer que heap no ejecutable Aleatorización de la asignación de memoria en el heap. 47 Heap overflow (ejemplo) typedef struct_vuln_struct { char buff[MAX_LEN]; int (*cmp) (char *, char *); } vuln; /* antes se hizo vuln * t = malloc(sizeof(vuln) ); .. foo(t, “..”, “..”); .. */ int foo ( vuln * s, char * one, char * two ) { strcpy(s->buff, one); strcat(s->buff, two); return s->cmp(s->buff,”file://archivo”); } vulnerabilidad => strlen(one) + strlen(two) < MAX_LEN 48 Variantes de Heap overflow C++: Overflow sobre el objeto vtable que contiene los punteros a métodos padres. Overflow en objetos adyacentes No precisamente el buffer esta con un puntero a función pero es asignado cerca de uno en el heap. Overflow sobre metadatos (por ejemplo en estructura de datos). 49 Otros overflow Integer overflow Pueden llenar datos para: Modificar clave secreta, el estado de una variable, cadena que pueden ser interpretadas por otros programas. Read overflow Leer más allá del buffer, por ejemplo buscando claves Heartbleed (2014): 600000 servidores en Internet. Podían leer más allá del tamaño recibido y así obtener el contenido pasado del buffer(passwords, claves criptográficas, etc) Stale memory Utilizar un puntero que ya fue liberado (desreferenciar un puntero peligroso) Internet Explorer (2010): Ver boletin 979352. Link: https://technet.microsoft.com/en-us/library/security/979352.aspx 50 Return-to-libc Shellcode sin inyección de código 51 Ejemplo: Ataque de return-to-libc ret transfiere el control a system, el cual encuentra argumentos en el Stack Sobreescribir el return address con la dirección de una función en libc Configurar el ret. add. y los argumentos ret llamará a una función en libc No hay inyección de código … ptr a argv “/bin/sh” argc return addr &system caller’s ebp %ebp buf (64 bytes) argv[1] buf %esp 52 Defensas contra ataques de bajo nivel Cuestiones comunes en estos ataques Control de algunos datos usados por el programa Estos datos permiten acceso no intencional a alguna área de la memoria en el programa Memoria segura y tipo seguro Propiedades que si son satisfechas asegura que una aplicación es inmune a ataques de memoria. C y C++ no son de memoria segura por defecto. Defensas automáticas Canarios de Pila (stack canaries) ASLR (Address Space Layout Randomization) Ataque de ROP (return-oriented-programming) Defensa: Control de Integridad de Flujo (CFI) Codificación Segura 53 Memoria segura Solo crea punteros a través de formas estándar p = malloc(), p = &x, p = &buf[5], etc, Solo usa un puntero para acceder a memoria que pertenece al puntero. Dos conceptos asociados: Seguridad temporal. Seguridad espacial. 54 Seguridad espacial Se accede solo a lo que le pertenece al puntero Un puntero tiene tres propiedades (p, b, e) p = es el puntero actual b = es la base de la región de memoria que puede acceder e = es la extensión o límite de la región El acceso es permitido si b <= p <= e – sizeof(tipo(p)) 55 Seguridad espacial Ejemplo 1 int x ; int * y int * z *y = 3; *z = 3; = &x; = y + 1; // sizeof(int) = 4 // p = &x b=&x e=&x+4 // p = &x+4 b=&x e=&x+4 // OK &x <= &x <= (&x+4)-4 // Mal &x <= &x+4 <= (&x+4)-4 56 Seguridad espacial Ejemplo 2 struct foo { char buf[4]; int x; } struct foo f = {“cat”, 5}; char * y = &f.buf; // p=b=&f.buf, e=&f.buf+4 y[3] = ‘s’; // Ok p=&f.buf+3 <= (&f.buf+4)-1 y[4] = ‘y’; // Mal p=&f.buf+4 <= (&f.buf+4)-1 57 Seguridad temporal Una violación a la seguridad temporal ocurre cuando se trata de acceder a memoria indefinida La seguridad espacial: Asegura que fue en una región legal La seguridad temporal Asegura que esa región es aún válida Memoria definida o indefinida Definida: significa asignada Indefinida: significa desasignada, no inicializada o no asignada. 58 Seguridad temporal (ejemplo) Usar un puntero ya liberado, retornar un puntero de una variable local. // puntero usado cuando ya no es válido int * p = malloc(sizeof(int)); *p = 5; free(p); printf(“%d\n”, *p); // puntero utilizado y no fue inicializado int * z ; *z = 5; 59 Memoria segura Los lenguajes modernos son de memoria segura y también de tipo seguro (Java, Python, Go, etc) Progreso para C/C++ CCured(2004): 1.5x más lento No chequea en librerías Compilador rechaza muchos programas seguros Softbound/CETS(2010): 2.16x más lento Completo chequeo Flexible A nivel de HW Intel MPX (feb/2015) Soporta chequeo mucho más rápido, registros con límites de punteros 60 Tipo seguro Cada objeto tiene asociado un tipo (int, puntero a int, puntero a función, etc) Operaciones en un objeto son siempre compatibles con el tipo del objeto Programas de tipo seguro no fallan en tiempo de ejecución. Tipo seguro es más fuerte que Memoria segura int (*cmp) (char *, char*); int * p = (int *) malloc (sizeof(int)); *p = 1; cmp = (int(*) (char *, char*)) p; cmp(“hola”, “que tal”); Es de memoria segura pero no de tipo seguro 61 Tipo seguro Lenguajes de tipeo dinámico como Ruby o Python son de tipo seguro. Cada objeto tiene un solo tipo = Dinámico Cada operación en un objeto dinámico es permitido pero puede NO esta implementado Entonces ocurre una excepción!! Y esto es mucho mejor que dejar que se igual se continue!! 62 Tipos por seguridad Puede relacionar directamente a propiedades de seguridad. Se colocan invariantes en el programa Ejemplo: Java with Information Flow (JIF) //Alice es dueño, Bob puede leer int {Alice->Bob} x; // Alice y Chuck es dueño, Bob puede leer int {Alice->Bob, Chuck} y; x = y; // Ok y = x; // Mal 63 Tipo seguro ¿Porqué no todos son de tipo seguro? Por cuestiones de performance sobre todo Hacia el futuro Nuevos lenguajes ayudarían a mantener iguales prestaciones de C y C++ pero siendo de tipo seguro Google => Go Mozilla => Rust Apple => Swift Podrían ser el futuro de la programación a bajo nivel. 64 Evitando los exploits ¿Qué hacemos mientras C se vuelva de memoria segura? Dos defensas básicas Hacer que la explotación sea difícil Considerando los pasos, hacer que uno o más sean difíciles o imposibles. Evitar el bug por completo Prácticas de codificación segura Revisión de código / Testing por seguridad (análisis estáticos, dinámicos, test de penetración, etc) Estrategias son complementarias Evitar bugs pero agregar protección IDEA ya vista en el proceso de desarrollo software seguro 65 Recordando el stack smashing Pasos: Colocar código de máquina en memoria (sin bytes nulos) Obtener el %eip y apuntar el código de ataque Encontrar la dirección de retorno ¿Cómo hacer estos pasos más dificultosos para el atacante? Mejor caso: cambiar librerías, compilador y S.O. No cambio el código El cambio/arreglo está en la arquitectura de diseño y no en el código 66 Primera técnica: Canarios Historia (porqué canarios?) Override %eip %ebp text buffer %eip &arg1 &arg2 Canario de stack Si el ataque sobreescribe el buffer también lo hace con el canario. En este caso se verifica el valor y sino coinciden se aborta Valor del canario: randómico usualmente. Protejo la colocación de código en el stack 67 Stack smashing (pasos) Pasos Colocar código de máquina en memoria (sin bytes nulos) Canarios Obtener el %eip y apuntar el código de ataque Defensa: hacer que el stack (y el heap)no sean ejecutables DEP (Data Execution Prevention) – Linux, OS X, MS, iOS, Android NX bit (No-Execute) – XD (Executable disable) Intel – EVP (Enhanced Virus protection) AMD – XN (Execute Never) ARM 68 Defensa contra escritura de %eip ASLR (Address Space Layout Randomization) Randómicamente colocar librerías estándar y otros elementos en memoria, haciendo difícil de adivinar También previene encontrar el valor de retorno y el ataque return-to-libc Disponible en los S.O. modernos Linux (2004) OpenBSD (2006) Windows (2007) Mac OS (2007 => para librerias 2011 => para cualquier programa) Android (2012) IOS (2011) No todo es perfecto Solo desplaza el inicio(offset) de las áreas de memoria y no las localizaciones dentro de ellas Solo para librerías y a veces para programas (depende del compilador) Necesidad de suficiente aleatoriedad Se mostró que un ataque por fuerza bruta puede tener éxito. En 216 segundos en promedio se logró romper la defensa en un HW del 2004. Un HW de 64 bits puede proveer más seguridad 69 Como evito la protección ASLR Defensa: evitar uso de libc completamente Respuesta: Construir la funcionalidad necesaria con código que se encuentra en el área de text ROP (Return oriented programming) 70 ROP Inventada en 2007 por Hovav Shacham (paper académico) Idea: antes de usar una función de libc para ejecutar el shellcode, concatenar piezas de código existentes llamadas gadgets. Un gadget es una secuencia de instrucción que termina con al instrucción ret . La pila sirve como el código %esp : apunta al programa Un gadget invocado via ret Un gadget toma sus argumentos vía pop ¿Cómo puedo encontrar un gadget para construir un exploit? Automatizar la búsqueda en el binario: https//github.com/0vercl0k/rp ¿Son suficientes para hacer algo interesante? Shacham encontró código significante para libc (Turing Completo) E. Schwart (USENIX Security 2011) automatizó la creación de shellcode basado en gadgets Link 71 Blind ROP La aleatorización de la ubicación del código en una máquina de 64 bits hace que el ataque muy difícil. Respuesta: blind ROP Introducido en 2014 en IEEE Security Conference Permite escribir el exploit sin poseer el binario Requiere de un stack overflow y un servicio que se restaure luego del crash. Se basa en que los servidores generan un nuevo proceso luego del crash, sin aleatorización (por ejemplo el servidor nginx). Más información en http://www.scs.stanford.edu/brop/ 72 Resumen ataques y defensas 73 Detección de comportamiento malicioso Observar el comportamiento de un programa. Si no esta haciendo lo esperado entonces puede estar comprometido Desafíos Definir el comportamiento esperado Detectar desviaciones eficientemente Evitar que el detector se vea comprometido Control Flow Integrity (CFI) permite esto Componentes Control Flow Graph (CFG) Inline Reference Monitor (IRM) Inmutabilidad 74 Control Flow Integrity CFI clásico -> 2005 16% de overhead en promedio 45% de overhead en el peor caso No modular (sin soporte de librerías dinámicas) Modular CFI (2014) 5% overhead en promedio 12% overhead en el peor caso Puede eliminar 95.75% de gadgets ROP Modular con compilación separada Drawback: solo C (LLVM compiler Link) Referencia de CFI (2005): Control Flow Integrity.Principles, Implementations and Applications 75 Código seguro en C Referencias/Guías: SEI CERT C Coding standard https://www.securecoding.cert.org/confluence/display/c/SEI +CERT+C+Coding+Standard Incluye otros lenguajes C++, Java, Perl. Secure Programming HOWTO. David Wheeler Matt Bishop. Robust Programming http://nob.cs.ucdavis.edu/bishop/secprog/robust.html Combinar con revisión de código y testing. 76 Regla: forzar la conformidad de la entrada char digit_to_char(int i) { char convert[] = “0123456789”; if ( i < 0 || i > 9 ) return ‘?’; return convert[i]; Conformidad de la entrada Buffer overflow } Confiar en la entrada recibida es una fuente recurrente de vulnerabilidades 77 Regla: Mejor lanzar una excepción que ejecutar código malicioso Principio: Codificación robusta 78 Funciones comunes en C que son inseguras Función Descripción gets( char * str) Lee una línea de la entrada estándar en str sprintf(char * str, char * format, …) Crea str de acuerdo al formato y variables strcat(char * dest, char * src) Concatena el contendido de src a dest strcpy(char * dest, char * src) Copia src a dest vsprintf(char * str, char * fmt, va_list ap) Crea str de acuerdo al formato y variables 79 Reglas: Usar funciones de cadenas seguras strcpy => strlcpy strcat => strlcat strncat => strlcat strncpy => strlcpy sprintf => snprintf vsprintf => vsnprintf gets => fgets No olvidar terminador NUL Entender la aritmética de punteros Defensa contra punteros peligrosos Usar NULL luego de free() Manejar la memoria apropiadamente Usar librerías seguras Usar un asignador de memoria seguro (diferente a malloc seguramente) 80 Ejercicio #1 Dado el siguiente código, completar con la dirección apropiada del shellcode considerando una máquina de 32 bits. char shellcode[]= “\x31\xc0” “\x50” “\x68””//sh” “\x68””/bin” “\x89\xe3” “\x50” “\x53” “\x89\xe1” “\x99” “\xb0\x0b” “\xcd\x80”; int function(){ int * ret = _________________________ (*ret) = (int) shellcode; } int main() { function(); } 81 Ejercicio #2 ¿Qué esta mal en este código? ¿Cuál es el potencial problema de seguridad que encuentra? #define MAX_BUF 256 void BadCode(char * input ) { short len; char buf[MAX_BUF]; len = strlen(input); if ( len < MAX_BUF ) strcpy(buf, input); } 82 Bibliografía/Referencias CWE/SANS Top 25 most dangerous software errors (http://www.sans.org/top25-software-errors y cwe.mitre.org/top25) Belk M. et al. Fundamentals Practices for Secure Software Development. SAFECode 2nd Edition. 2011. W. Stalling & L. Brown. Computer Security. Principles and Practices. 2nd Edition. Pearson Educ. Inc. 2012. M. Howard , D. LeBlanc y J. Viega. 19 puntos críticos sobre seguridad de software. McGraw Hill.2007 Referencias puestas en cada diapositiva. 83 ¡Gracias! ¿Preguntas? 84