Seguridad Informática Seguridad software I Ramón Hermoso y Matteo Vasirani Grado en Ingeniería Informática
1 Programación y seguridad 2 Buffer overflow 3 Inyección SQL 4 Condición de carreras 5 Errores de control de acceso 6 Errores en números aleatorios
1 Programación y seguridad 2 Buffer overflow 3 Inyección SQL 4 Condición de carreras 5 Errores de control de acceso 6 Errores en números aleatorios
Programación y seguridad La relación entre programación y seguridad se puede ver desde dos perspectivas Programar la seguridad Desarrollar funcionalidades de seguridad específicas, como encriptación, firmas digitales, firewall, etc Programar con seguridad Desarrollar código de manera segura, para que los programas no contengan vulnerabilidades que un atacante pueda explotar En las próximas dos sesiones nos centraremos en el segundo aspecto
Programación y seguridad Las empresas software sufren una presión económica que hace que sigan el modelo early-release-and-patch Los parches muchas veces no se aplican Los parches a menudo se ocupan de los sintomas, no de las causas Los parches causan la explosión de versiones y problemas de compatibilidad Una mejor práctica es eliminar los bugs de seguridad antes de poner a la venta el software Dificil: software siempre más complejo, muchas líneas de código Costoso: hacen falta muchos ojos que busquen las vulnerabilidades (p.e. open source)
1 Programación y seguridad 2 Buffer overflow 3 Inyección SQL 4 Condición de carreras 5 Errores de control de acceso 6 Errores en números aleatorios
Buffer overflow Algunos programas donde se han encontrado vulnerabilidades de buffer overflow mount, sendmail, lpr, cron, login, sendmail (otra vez), rlogin, passwd, sendmail (de nuevo?), lpd, xterm, elm, ps, df, at, smbmount, bash, telnetd, rsh, ping, traceroute, Windows95 ntftp, IMail POP3 server for WindowsNT, Samba, pine, Real Player, imapd, Outlook Express, mutt, MSIE, nslookup, Netscape,... Hasta 2004, responsable de casi la mitad de las vulnerabilidades descubiertas Error trivial que puede tener un impacto enorme en la seguridad de un sistema
Causas de un buffer overflow No comprobación de la dimensión en bytes de los argumentos que se pasan a una función, la cual los almacena en arrays de dimensión fija Típicamente un buffer overflow ocurre con arrays de caracteres Por ejemplo, la función strcpy() de la librería estandar C no comprueba el tamaño de origen y destino Los buffer overflow pueden corromper datos del programa o ejecutar código Noticia chistosa: un buffer overflow hace explotar un cohete! En el 1996, el cohete Ariane 5 de la ESA explotó después del despegue porque un programa intentó copiar un número de 64 bit en un buffer de memoria de 16 bit
Memoria La mayoría de los lenguajes de programación dividen la memoria en cuatro partes: 1 Heap Almacena las estructuras de datos que se crean a tiempo de ejecución, como los objetos con sus atributos en Java o los buffers creados con la instrucción malloc() en C 2 Stack Almacena los métodos, las variables locales y las referencias a objetos 3 Code Almacena el código ejecutable 4 Static Almacena los datos y los métodos estáticos
Ejemplo public void method1(){ int x = 20; method2(10); } public void method2(int y){ int z = 10; Account ref = new Account(); }... public class Account{ private float amount = 0; private float rate = 0.01; }
Ejemplo public void method1(){ int x = 20; method2(10); } public void method2(int y){ int z = 10; Account ref = new Account(); }... public class Account{ private float amount = 0; private float rate = 0.01; }
Ejemplo public void method1(){ int x = 20; method2(10); } public void method2(int y){ int z = 10; Account ref = new Account(); }... public class Account{ private float amount = 0; private float rate = 0.01; } Cuando el método method2(int y) termina...
Ejemplo public void method1(){ int x = 20; method2(10); } public void method2(int y){ int z = 10; Account ref = new Account(); }... public class Account{ private float amount = 0; private float rate = 0.01; } Cuando el método method2(int y) termina...
Funcionamiento normal del stack
Funcionamiento normal del stack
Funcionamiento normal del stack
Funcionamiento del stack con buffer overflow char buf1[80]; int buf2; void vulnerable(char* param1, int param2){ buf2 = param2; strcpy(buf1, param1); } Qué pasa si param1 contiene más de 80 bytes?
Funcionamiento del stack con buffer overflow
Funcionamiento del stack con buffer overflow char buf1[80]; int authenticated = 0; void vulnerable(char* param1){ strcpy(buf1, param1); } Qué pasa si param1 contiene 81 bytes, y el 81ésimo byte no es zero?
Funcionamiento del stack con buffer overflow
Funcionamiento del stack con buffer overflow
Buffer overflow Típicamente el programa que se quiere ejecutar es una shell (/bin/sh) execve( /bin/sh ); Pero el código está en el stack, allí solo puede haber lenguaje máquina char shellcode[] = \xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88 \x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3 \x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31 \xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff \xff\xff/bin/sh ;
Buffer overflow Para aumentar la probabilidad de poder ejecutar el comando en lenguaje máquina, se inserta ante de éste un NOP-sled Un número arbitrario de instrucciones NOP que hagan de trineo hasta el comando execve( /bin/sh );
Buffer overflow Detectar si se puede hacer un buffer overflow a base de intentos Se pasa un parametro de dimensión mayor de lo que espera Segmentation fault Explotar un buffer overflow requiere mucha artesanía Entre el buffer que recibe el input del atacante y el espacio donde se almacena el return pointer puede haber datos, variables... Si el programa atacado por un buffer overflow es propiedad de root, la shell que se ejecuta tendrá UID = 0 El atacante tiene completo control sobre el sistema!!!
Defensas para un buffer overflow 1 Eliminar los posibles buffer overflow Comprobar la dimensión de los parámetros pasados por el usuario Inspeccionar el código para encontrar posibles vulnerabilidades Usar lenguajes de programación y librerias que comprueben automáticamente la dimensión de los parámetros (Java)
Defensas para un buffer overflow 2 Dejar los posibles buffer overflow, pero evitar su ejecución Deshabilitar la ejecución de código en el stack
Defensas para un buffer overflow 3 Comprobar posibles buffer overflow a tiempo de ejecución (StackGuard) Genera una string aleatoria cuando arranca el programa (canario) Inserta el canario en todos los stack frames Verifica la integridad del canario antes de devolver el control
1 Programación y seguridad 2 Buffer overflow 3 Inyección SQL 4 Condición de carreras 5 Errores de control de acceso 6 Errores en números aleatorios
Inyección SQL Las mayoría de las aplicaciones hacen uso de bases de datos para almacenar la información necesaria al funcionamiento del programa Estas aplicaciones necesitan datos proporcionados por el usuario, que se usan para consultar la base de datos Ejemplo: String user = readinput(); String query = "SELECT id FROM Person " + "WHERE username = " + user; Qué pasa si user es una cadena de caracteres maliciosa que cambia el sentido de la consulta?
Inyección SQL
Inyección SQL
Inyección SQL CardSystems Solutions Compañía que ofrecía servicios de pagos con tarjeta de crédito Ataque basado en SQL en 2005 263.000 números de tarjetas de crédito robadas (!) 43 millones de tarjetas estaban en peligro (!!) Los números estaban almacenados sin cifrar (!!!)
Inyección SQL Ejemplo de consulta en Java: public void login(connection conn, String user, String pwd) { Statement stmt = conn.createstatement(); ResultSet rs = stmt.executequery( "SELECT * FROM Users " + "WHERE username = " + user + " AND password = "+ pwd + " ); if(rs.next()) { /* login OK */ }... }
Inyección SQL
Inyección SQL La secuencia -- hace que el resto de la consulta sea ignorada El ResultSet siempre contendrá datos login garantizado
Inyección SQL El atacante consigue alterar la base de datos Añadir usuarios, eliminar usuarios, resetear contraseñas...
Defensas contra inyección SQL Nunca construir las consultas como concatenaciones de string String query = "SELECT * FROM Users WHERE username = " + user; Usar consultas preparadas y parametrizables Usar implementaciones de SQL que impidan la ejecución de varias consultas a la vez (Java) Veamos inyección SQL en acción
1 Programación y seguridad 2 Buffer overflow 3 Inyección SQL 4 Condición de carreras 5 Errores de control de acceso 6 Errores en números aleatorios
Condición de carrera CTSS (Compatible Time-Sharing System), MIT, 1960-1970 Un usuario recibe el fichero de contraseña como mensaje del día!!! Los administradores del sistema trabajaban todos en el mismo directorio Cuando un usuario invocaba el editor de texto, se creaba un fichero con nombre fijo (p.e., foo.txt)
Condición de carrera
Condición de carrera Una condición de carrera ocurre cuando un programa no funciona como esperado debido al orden de una secuencia de eventos que operan sobre un mismo recurso Ejemplo: ExploitRace.java
Condición de carrera Si un programa se ejecuta con privilegios de root, un atacante podría empezar una carrera contra este programa, con la intención de cambiar el comportamiento esperado Ejemplo: buggy script.sh touch $INPUT FILE ## otras cosas chown $USER:$GROUP $INPUT FILE Qué pasa si entre la ejecución de touch y chown, el atacante ejecuta: ln -f /etc/shadow $INPUT FILE
Condición de carrera
Defensas para una condición de carrera Sincronizar el acceso a recursos compartidos por varios threads Ejemplo: NoExploitRace.java Cuidado con el uso de synchronized por que puede causar deadlock y empeorar sensiblemente las prestaciones Reducir el TOCTTOU (Time of Check to Time of Use) Veamos condiciones de carrera en acción
1 Programación y seguridad 2 Buffer overflow 3 Inyección SQL 4 Condición de carreras 5 Errores de control de acceso 6 Errores en números aleatorios
Errores de control de acceso La mayoría de los ataques se aprovecha de los fallos en el principio del privilegio mínimo Un usuario/proceso tiene acceso al conjunto mínimo de recursos necesarios para su funcionamiento Una mala gestión de los permisos puede conceder a los usuarios más privilegios de lós que necesitan El ataque más típico es la escalada de privilegios
Errores de control de acceso Muchos programas se ejecutan con privilegios de root A pesar de que necesitan hacer sólo algunas operaciones con privilegios de root Cuál es el problema? Los programas con privilegios de root son el principal objetivo de los atacantes Si hay una vulnerabilidad en aquellas partes del programa accesibles a cualquiera, un usuario puede conseguir escalar privilegios
Defensas para errores de control de acceso Deshacerse de los privilegios de root cuanto antes Ejemplo: un programa que necesita los privilegios para ponerse en escucha en un puerto < 1024 Deshacerse de los privilegios luego después de establecer la conexión Si un atacante encuentra un bug en alguna parte del código después de esta operación, no puede escalar privilegios Cómo deshacerse de los privilegios? seteuid(newid) Establece el UID del proceso a lo largo de la ejecución (effective user ID)
1 Programación y seguridad 2 Buffer overflow 3 Inyección SQL 4 Condición de carreras 5 Errores de control de acceso 6 Errores en números aleatorios
Errores en números aleatorios Las aplicaciones de seguridad necesitan números aleatorios para, por ejemplo, generar claves (se verá en Criptografía) Muchos ataques se aprovechan de la predictibilidad de los generadores de números (supuestamente) aleatorios El atacante intenta adivinar el siguiente número, dada una secuencia histórica
Defensas para errores en números aleatorios Semilla aleatoria generador de números aleatorios Un generador de números aleatorios tiene que generar números uniformemente distribuidos La semilla tiene que ser imposible de adivinar Buenas semillas: teclas, movimiento del ratón, ruido eléctrico, tiempo de acceso al disco... Malas semillas: reloj del sistema, dirección Ethernet, tiempo de latencia de la red u otras fuentes que el atacante pueda influenciar