Análisis/Testing de SW por Seguridad
Transcripción
Análisis/Testing de SW por Seguridad
Análisis/Testing de SW por Seguridad 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 ([email protected]) NIDTEC - Núcleo de Investigación y Desarrollo Tecnológico - FPUNA Contenido Introducción y motivación Técnicas de análisis y testing para hallazgo de vulnerabilidades Fuzzing (webscarab) Análisis estático/dinámico Test de penetración. Herramientas disponibles para evaluar seguridad del código 2 Introducción El software juega un rol principal en la provisión de seguridad y el la mayor fuente de problemas de seguridad El SW es el eslabón más débil en la cadena de seguridad, con la posible excepción del factor humano No hay bala de plata La criptografía o las prestaciones especiales de seguridad no resuelven mágicamente el problema Software security ≠ security Software “Si piensas que tu problema se resuelve con criptografía, no entiendes la criptografía y no entiendes tu problema” [Bruce Schneier https://www.schneier.com/about.html] 3 Introducción La seguridad es una propiedad emergente del sistema completo Semejante a la calidad Los aspectos de seguridad deben ser parte integral del diseño, desde el inicio. La seguridad es siempre un aspecto secundario El aspecto principal es proveer alguna funcionalidad o servicio, los riesgos asociados es un derivado/aspecto secundario. Trade-off entre seguridad y funcionalidad donde típicamente la seguridad..pierde. 4 5 Funcionalidad vs Seguridad Funcionalidad es acerca de que el software debe hacer y la seguridad es acerca de que no debe hacer Batallas perdidas? Sistemas operativos Con grandes y complejos SO, grandes superficies de ataques Lenguajes de programación Cada vez más fáciles, eficientes, pero con mecanismos inseguros y propenso a errores Navegadores Con muchísimos plug-ins para varios formatos Etc, Etc 6 Más debilidades del software Ejecutándose en una gran y complicada infraestructura: S.O., plataforma, navegadores, librerias, APIs, etc Construido con complejos lenguajes De programación pero también SQL, HTML, XML, CSS, etc Usando varias herramientas Compiladores, IDEs, preprocesadores, etc 7 Mapa conceptual de seguridad Quieren maximizar Propietarios Disponibilidad/Utilidad Quieren minimizar imponen Contramedidas reducen Pueden tener requieren de Vulnerabilidades Atacantes explotan Conduce a aumentan Riesgos a Amenazas aumentan Valores Quieren abusar 8 Terminología Muchos términos similares: desperfecto/falla (flaw), vulnerabilidad, bug, error, defecto de código. Una falla/debilidad(flaw) de seguridad Usualmente en fase de diseño Una vulnerabilidad de seguridad Explotable por un atacante La vulnerabilidad del software es introducida por Una falla en el diseño Una falla en la implementación (bug, defecto de código) 9 Existen dos tipos de fallas de implementación Bugs que pueden ser comprendidos mirando el código y entendiendo que se quiso hacer: Errores de tipeo, confusión de nombre de variables, etc Errores lógicos Problemas de bajo nivel que solo pueden ser descubiertos si conoce la plataforma subyacente de ejecución del programa, por ejemplo: Buffer overflow, integer overflow (en binarios) SQL Injection, XSS, CSRF (en aplicaciones web) 10 Estado de la seguridad en aplicaciones Malas noticias La gente continua cometiendo los mismos tipos de errores Buenas noticias La gente continua cometiendo los mismos tipos de errores 11 Combatiendo la “inseguridad” en el software Conociendo los errores estándar es crucial para prevenirlos Dependientes del lenguaje de programación, de la plataforma y del tipo de aplicación Muchísima información disponible… Por ejemplo mirar el libro de 21 errores de seguridad en software por Howard, LeBlanc y Viega. PDF Pero no es suficiente, la seguridad debe ser tenida en cuenta desde el principio, a través del ciclo de vida de desarrollo del software (lo que miramos en clases pasadas). 12 Metodologías para desarrollar software seguro desde el inicio McGraw’s Touchpoint BSIMM Building Security in – Maturity Model Microsoft SDL OpenSAMM (Software Assurance Maturity Model) Ya miramos en clases pasadas!! 13 Puntos de falla de seguridad - Ejemplo int balance; <= debe ser >= ¿Qué pasa si monto void debito (int monto){ es negativo? if ( balance <= monto ) balance = balance - monto; else printf(“Saldo insuficiente\n”); } void credito (int monto) { balance = balance + monto; } ¿Qué pasa si monto es muy grande para un int? 14 Diferentes tipos de fallas de implementación ¿Qué pasa si monto es negativo? 1. Posible falta de validación de entrada del usuario Puede ser una falla de diseño? <= debe ser >= 2. Error simple de lógica ¿Qué pasa si monto es muy grande para un int? 3. Potencial problema dependiendo de la plataforma de ejecución del programa 15 ¿Porqué el software tiene vulnerabilidades? Programadores son humanos Humanos cometen errores Programadores no tienen capacitación en seguridad Los lenguajes de programación no están bien diseñados para seguridad 16 ¿Qué podemos hacer? Programadores son humanos Humanos cometen errores Usar herramientas Programadores no tienen capacitación en seguridad Aprender sobre clases comunes de errores Los lenguajes de programación no están bien diseñados para seguridad Utilizar mejores lenguajes 17 Encontrar bugs Atacantes Encuentra vulnerabilidades Crear un exploit para esa vulnerabilidad Utiliza el exploit para comprometer máquinas/sistemas Exploits valen dinero!! Encontrar vulnerabilidad Crear exploit Comprometer US$ 18 Mercado de 0day (entre US$ 10K y 100K o más!!) 19 Encontrar bugs Defensores Encontrar vulnerabilidades y eliminarlas Mejorar seguridad en el software Más fácil y barato arreglar vulnerabilidades antes del despliegue del software Luego del despliegue: el “parcheo” es caro Idealmente probamos que un programa esta libre de vulnerabilidades Encontrar vulnerabilidad Arreglar Vulnerabilidad Arreglo interno Costo bajo Parche Costo alto 20 Técnicas y abordajes para encontrar vulnerabilidades Generación automática de tests Fuzzing Análisis estático Verificación de Programas Ejecución simbólica dinámica Baja cobertura Pocos falsos positivos Muchos falsos negativos Alta cobertura Pocos falsos negativos Muchos falsos positivos 21 Testing Es un elemento crítico en el ciclo de desarrollo de software Llamado control de calidad del software o SQA (software quality assurance) Metas básicas: validación y verificación Validación: construimos el producto correcto? Verificación: cumple “X” con su especificación? Donde X puede ser código, modelo, diagrama, requerimiento, etc. En cada estado, necesitamos verificar que lo que producimos representa exactamente su especificación 22 Terminología en testing Un error es una falta hecha por un ingeniero Debido a una mala interpretación de un requerimiento o especificación de diseño Un falla/bug es una manifestación del error en el código Un failure es una salida/comportamiento incorrecto causado por la ejecución del bug Puede ocurrir inmediatamente o mucho despues de la ejecución El testing intenta hacer emerger los “failures” en el software ¿Si el sistema pasa todos los tests, esta libre de todos los bugs? 23 NOP Bugs puedes estar escondidos en porciones de código que son ejecutados raramente Testing solo prueba la existencia de bugs no su ausencia. NO todos los bugs ocasionan “failures”. Sin embargo, si hacemos un buen trabajo en crear un conjunto de pruebas que Cubre todas las capacidades funcionales del sistema Y cubre todo el código usando una métrica como el de cobertura de branch Entonces, incrementa la confianza acerca de la calidad del sistema desarrollado. 24 Todos los posibles estados/comportamientos del sistema 25 Buscando bugs 26 La literatura indica dividir el espacio en comportamientos similares y tener muestras de cada partición 27 Testing – tipos (Ingeniería de SW) Unitarios Por módulo, función. Cubre aspecto de bajo nivel. Integración Chequea la interacción entre módulos Sistema Prueba el sistema completo de forma a verificar que todas las funcionalidades han sido implementadas Aceptación Realizado por el usuario para probar el sistema entregado. 28 Tipos de test al código Caja negra (black box) No se tiene el fuente del sistema. Prueba que el sistema se comporta según su especificación Caja gris (grey box) Se tiene información de la arquitectura del sistema Caja blanca (white box) Se tiene acceso al código fuente, podemos estar seguros de cubrir todos los aspectos del código: sentencias, branches, paths, etc. 29 Black box testing 30 White box Miramos Cobertura de código Cobertura de sentencias (probar que todas las sentencias se hayan ejecutado) Cobertura de Branch (probar cada arista del grafo de control de flujo del programa que haya ejecutado una vez) Cobertura de condición (probar que todas las combinaciones condicionales se hayan cubierto) Cobertura de camino(probar todos los caminos del grafo de control de flujo según ciertas heurísticas). Manejo de errores Verificamos si coincide con la documentación Manejo apropiado de recursos 31 Ejemplo del CFG (Control Flow Graph) 32 Coberturas Sentencias Branch Condiciones Paths 33 Testing en seguridad Testing por funcionalidad: Pruebo por inputs usuales o valores límites Testing por seguridad: Prueba por entradas totalmente anómalas y con intenciones maliciosas generalmente. Una técnica es la de fuzzing. 34 Fuzzing 35 Test fuzzing de black-box Dado un programa, simplemente alimentarlo con entradas randómicas y verificar si falla. Ventaja: muy fácil Desventaja: ineficiente Las entradas requieren a veces estructura, puede que las generadas aleatoriamente estén malformadas La probabilidad de encontrar una entrada que haga mal funcionar el software es bastante baja. 36 Regresión vs. Fuzzing Regresión: Ejecutar el programa con muchos inputs normales, buscando por la entrada mala. Previene que usuarios normales encuentren errores Fuzzing: Ejecutar el programa con muchos inputs anormales, buscando por la entrada mala. Previene que los atacantes encuentren errores explotables 37 Fuzzing: mejora I Fuzzing basado en mutación Tomar una entrada normal y perturbarla randómicamente No asume conocimiento sobre la estructura de la entrada Anomalías pueden ser completamente aleatorias o seguir alguna heurística (ej. remover NUL, desplazar caracteres hacia adelante, etc) Ejemplo: zzuf (mirar demo) 38 Fuzzing: mejora II Fuzzing basado en generación Son generados en base a alguna descripción de formato: RFC, documentación, etc. Anomalías son agregadas en lugares posibles de la entrada Conocimiento el protocolo debe dar un mejor resultado que el fuzzing aleatorio 39 Herramientas fuzzy Generación de la entrada Usando algún fuzzer Veremos como utilizar OWASP ZAP para generar entradas para nuestro sistema de prueba Inyección de la entrada Simple (usar el archivo modificado, cambiar el paquete, etc), modificar el cliente para que invoque al fuzzer adecuadamente ó usando un framework de fuzzing Detección de bug Ver si el programa falla, correr bajo un detector de falla de memoria dinámica (ejemplo valgrind) 40 Ejercicio #1 Indique cuantos tests (y dé un ejemplo) serán necesarios para el siguiente código a fin de realizar: (Haga antes CFG) Cobertura de sentencias Cobertura de branch Cobertura de caminos (cuantos caminos existen?) if ( a if ( b a = b = > 2 ) 2; > 2 ) 2; 41 Ejercicio #2 Considere el siguiente código: void mySafeCpy(char *dst, char* src){ if(dst && src) strcpy(dst, src); } Una cobertura de sentencias ¿garantiza encontrar el bug? Una cobertura de branch ¿garantiza encontrar el bug? Haga el CFG antes. 42 Análisis estático Los métodos tradicionales para encontrar errores Testing Inspección o revisión de código Algunos errores son difíciles de hallar con estos métodos, ya que Surgen en circunstancias inusuales/caminos de ejecución no comunes Buffer overflow, input invalidado, excepciones Envuelve no-determinismo Race-conditions 43 Testing Input .. int main(…) { --} Beneficios Es correcto? Output oráculo Fallas concretas proveen justificación de los errores y ayudan a repararlos. Problemas Caro, dificultoso, difícil de cubrir todos los caminos, no garantiza encontrar todos los errores. 44 Inspección de código o auditoría del código .. int main(…) { --} .. void f1(…) { --} .. int f2(…) { --} Beneficios El humano puede generalizar con una simple revisión Problemas Caro (con respecto al tiempo de una computadora), trabajoso y no hay garantías de cobertura. 45 Análisis estático Analiza el programa sin ejecutarlo (la inspección de código se le deja a la computadora) Beneficios Es de alta cobertura Problemas Puede analizar propiedades limitadas Puede errar/olvidar algunos problemas=>falsas alarmas Puede consumir mucho tiempo de ejecución 46 Análisis estático – usos típicos Para optimizar el código Detectar variables no utilizadas Eliminar código muerto Detectar expresiones usadas frecuentemente Descubrir métodos sin efectos colaterales Desreferencias de objetos válida (evitar el chequeo de null) Verificación De contratos explícitos o implícitos Propiedades funcionales Errores “mecanicos” 47 Análisis estático – usos típicos Para entender programas Inferir el tipo de una función Cálculo pre/pos, invariantes Requerimientos de memoria Reingeniería Mejorar la calidad/legibilidad de código Puede chequear si los programas adhieren a patrones de buenas prácticas. 48 Impacto del análisis estático Aunque analiza propiedades limitadas, las que analiza son muy útiles Elimina categoría de errores Los desarrolladores pueden concentrarse en razonamientos más profundos Ayuda/auxilia a mejores prácticas de desarrollo Desarrolla modelos de programación que evita errores en primer lugar Ayuda a los programadores a pensar acerca y como manifestar sus asunciones Por ejemplo con notaciones especiales para mejorar la precisión de la herramienta Adopción comercial en aumento 49 ¿Qué puede hacer el Análisis estático? El problema de la Parada Puedo escribir un analizador que pueda probar, para cualquier programa P y una entrada para él, que P terminará? Llamado Problema de la Parada (Halting Problem) .. int main(…) { --} ¿Siempre termina? P oráculo Desafortunadamente este problema es indecidible, es decir, es imposible escribir tal analizador. Demostrado por Alan Turing en los 30’s Wiki 50 Aunque no podemos determinar la terminación del programa podemos determinar algunas propiedades que son interesantes a nivel de seguridad Por ejempo a[i] -> está en los límites? Podemos eliminar muchos problemas de memoria Pero esta propiedad puede también transformarse en el problema de la Parada y volverse indecidible. Esto es, un verificador perfecto de límites de un arreglo puede resolver el Problema de la Parada, el cual es imposible!! 51 Entonces, ¿es el analizador estático posible? El perfecto NO pero uno útil SI a pesar de No terminación / puede fallar en terminarse a si mismo Falsas alarmas. Dice que hay error y no lo hay Errores no reportados (olvidados) que es diferente a “NO existen errores” La NO terminación es difícil de analizar así que las herramientas tienden a exhibir falsas alarmas y errores no capturados Se encuentran entre la solvencia o correctitud (soundness) y la completitud (completeness) 52 Cosas verdaderas Cosas que digo Un análisis trivial correcto: no digo nada completeness soundness Soundness (propiedad): si el AS (analizar estático) dice que X es verdadero, entonces X es verdadero) Completeness: si X es verdadero, entonces el AS dice que X es verdadero Cosas que digo Cosas verdaderas Un análisis trivial completo: digo todo!! 53 Ambos dicen exactamente el conjunto de cosas verdaderas. Pero por el problema de la indecibilidad no tenemos ambos casos al mismo tiempo así que sacrificamos uno de ellos Soundness: si se dice que el programa no tiene error realmente es así. Alarmas no implica errores Completeness: si se dice que el programa es erróneo, realmente lo es. Silencio no implica que NO hayan errores Muchos análisis interesantes no son ni soundness ni completeness (o ambos). Tienden hacia una u otra propiedad 54 Debido que el análisis estático perfecto es imposible la idea es construir herramientas útiles. Diseño del AS – tradeoff Precisión: modelar cuidadosamente el comportamiento de un programa para minimizar las falsas alarmas Escalabilidad: analizar exitosamente grandes programas. Comprensibilidad: entendibles por los humanos Si existe un estilo de código (Coding style) el análisis puede mejorarse. Ayuda a la precisión para buenos programas Evitar código enmarañado en nombre de la seguridad Falsas alarmas son vistas positivamente: reduce la complejidad vía la mejora hecha por el programador 55 Análisis de flujo (Flow analysis) Es un tipo particular de AS. Rastrea como los datos pueden fluir entre diferentes posiciones de memoria en un programa. Es interesante en términos de seguridad porque nos ayudan a encontrar errores que son causa de muchos ataques que se basan en confianza de entradas no validadas. La entrada de un usuario es siempre “contaminada”(tainted) Varios datos usados se esperan que sean no contaminadas (untainted) Ejemplos: Fuente de strcpy ( <= tamaño del buffer) Cadena de Formato de printf (no tiene especificadores de formato) Campo de formulario para contruir SQL (no contiene comandos SQL) 56 Formato de cadenas char * name = fgets(.., network_fd); .. printf(name); //..upss!! Atacante coloca en name = “%s%s%s” Atacante coloca en name = “..%n” para escribir en memoria (ataque de inyección) 57 El problema en los tipos Especificar nuestro requerimiento como un qualificador de tipo int printf( untainted char * fmt,..) tainted char * name = fgets(…); tainted: posiblemente controlado por el adversario untainted: no debe ser controlado por el adversario tainted char * name = fgets(.., network_fd); printf(name); // FAIL tainted untainted 58 Problema: Para todos los posibles inputs, probar que datos contaminados (tainted) nunca serán usados donde son esperados datos no contaminados (untainted) untainted tainted : trusted sink : untrusted source Una solución requiere inferir el flujo en el programa Que fuentes (source) puedan alcanzar los usos (sink) Si cualquier flujo es ilegal. Esto es, si una fuente (source) confiable puede fluir a un uso no confiable. 59 void f( tainted int); untainted int a = ; f(a); void g( untainted int); tainted int b = ; g(b); F acepta datos tainted o untainted g acepta solo datos untainted untainted <= tainted tainted <= untainted Es LEGAL Es ILEGAL Existe una relación de orden (lattice) tainted < untainted Se permite el flujo cuando se respeta el orden 60 Abordaje Pensar en el AS como una clase de inferencia de tipo Si no existe un qualificador, debemos inferirlo Pasos Crear un nombre por cada qualificador que falta (,) Por cada sentencia en el programa, generar restricciones ( de la forma q1<= q2) en posibles soluciones Sentencia x=y genera la restricción qy <= qx donde qy es el qualificador de y y qx es el qualificador de x. Resolver las restricciones para producir soluciones para , Una solución es una sustitución de qualificadores (como tainted/untainted) para los nombres (como ,) tal que todas las restricciones sean FLUJOS LEGALES Si no hay solución, nosotros tenemos (o podemos) un FLUJO ILEGAL. 61 Ejemplo completo int printf(untainted char * fmt,..); tainted char * fgets(..); 1er paso: asignar nombres char * name = fgets(.., network_id); char * x = name; 3er paso: resolver las restricciones printf(x); 2do paso: restricciones tainted <= <= <= untainted tainted <= <= <= untainted 1ra. Restr. requiere = tainted 2da. Restr. requiere = tainted 3ra. Restr. requiere tainted <= untainted Por tanto tenemos un flujo ILEGAL y no Existe una solución para y 62 Variantes del análisis anterior(no veremos en detalle) Insensibles al flujo (básicamente lo que vimos) Sensibles al flujo Agregamos precisión de flujos condicionales Sensible al camino(path) Más precisión pero requiere más recursos Sensible al contexto Llamadas entre funciones 63 Otros tipos de análisis estático Análisis de punteros (“points-to” analysis) Determina cuando los punteros apuntan a la misma región (aliases) Data flow analysis 1970. Sensible al flujo. Flujo de datos en variables del programas. Usado para optimización en compiladores. Análisis de vivacidad de variables Abstract interpretation 1970. Permite ejecutar un programa en base a abstracciones. Las abstracciones descartan información (detalles en diapositivas finales) 64 Análisis Estático en la práctica Comerciales / Fortify OpenSource 65 Detalles de AS OpenSource Splint (Lenguaje C) (www.splint.org) Detecta vulnerabilidades y errores comunes de seguridad (por ejemplo posible buffer overflow) Permite annotations (ejemplo /*@requires maxSet(s1) >= ( n - 1 ); @*/ FingBugs (para Java) (findbugs.sourceforge.net) Universidad de Maryland Identifica patrones comunes de errores Usado por Google hasta hoy día Detecta vulnerabilidades de seguridad 66 Próxima clase Análisis estático por interpretación abstracta (abstract interpretation) Ejecución simbólica Test de penetración Seguridad en base de datos 67 Bibliografía y referencias McGraw G. Software Security: Building Security in. Addison-Wesley. 2005. Brian Chess/Jacob West.Secure Programming with static analysis. Addison-Wesley. 2007. Cursos/Materiales sobre seguridad en: http://www.cs.ru.nl/E.Poll/ss/ http://www.cs.berkeley.edu/~dawnsong/teaching/f12cs161/syllabus.html http://realsearchgroup.org/SEMaterials/tutorials/index.php https://wiki.engr.illinois.edu/display/ece422sp13/Home http://www.utdallas.edu/~muratk/courses/dbsec09s.htm Software Security (University of Maryland) - Coursera 68 ¡Gracias! ¿Preguntas? 69 Análisis estático Recordando: es la revisión sistemática de una abstracción del espacio de estados del programa Sistemática Examinamos todos los caminos posibles dentro de cada función La exploración es exhaustiva Abstracción Solo se mantiene información relevante a la propiedad a inferir Por ejemplo: Signo de variables 70 AS - Abstracción Abstracción Solo se mantiene información relevante a la propiedad a inferir Por ejemplo: Signo de variables Enfocarse en la propiedad a analizar Qué aspecto me interesa? Control: secuencia de eventos, concurrencia, etc Datos: división por cero, consumo de memoria Seguridad Funcionalidad 71 AS – Ejemplo de abstracciones Me interesa saber si hay una posible desreferencia a null : Var ->{null,noNull,quizasNull} Me interesa evitar división por cero :Var -> {indef,Z, NZ, QZ} Puedo liberar la memoria usada por el iterador al salir del método : grafo de points-to Ver alcanzibilidad 72 AS - Abstracción Abstracción requiere aproximación ya que no maneja el estado completo No tenemos información real Ejemplo: naturales positivos 3–3=0 Abs(3) = NZ ¿Cuánto es NZ-NZ? Es Z o NZ => MZ 73 AS - análisis Control flow analysis If (b) {c = 5;} else { c=6;} inicializa c If (b) {c = 5;} else { d=6;} no inicializa Data flow analysis d = 5; c = d; inicializa c c = d; d = 5; no inicializa ¿Inicializa c en la siguiente expresión? If ( i < 5 ) { c = 5; } If ( i < 0 ) || ( (i * i) > 20) {c = 6;} Un AS puede reportar: NO sabe, un falso positivo, un falso negativo. 74 AS – Ejemplo de determinación de división por cero Veremos análisis de dataflow Elementos Grafo de control de flujo Valores abstractos Z, NZ, MZ (Zero, No Zero, May be Zero) Funciones de transferencia F(Z+NZ)=NZ F(Z+Z)=Z F(Z-Z)=Z F(NZ-NZ)=MZ 75 76 77 78 79 80 81 Diagrama de uso de un analizador estático 82