Sistema de corrección de vulnerabilidades en arquitecturas complejas: Implantación

JS
14 min readOct 15, 2017

--

# Objetivos

Nuestro principal objetivo es poder agilizar la corrección de fallos de seguridad reportados en nuestros sistemas, servicios y/o aplicaciones cumpliendo siempre con unos requerimientos mínimos que iremos definiendo para garantizar que todo funcione de manera correcta.

Se debe separar, al menos inicialmente, los diferentes elementos que
queremos abordar, y con ello me refiero a los siguientes elementos:

  • Sistemas
  • Servicios
  • Aplicaciones

Esto se debe tener claro puesto que en los ciclos de aplicación de parches debemos separar los mismos y tomarlos con tratamientos diferentes, al menos inicialmente, hasta que podamos homogeneizar el flujo con ambos o reducirlos a la mínima colaboración y/o supervisión humana y manual.

La separación de los mismos a nivel técnico viene dada debido a que no es lo mismo la desactivación de un algoritmo de cifrado o protocolo concreto como podría pasar con cifrados de tipo DES/3DES y protocolos TLS 1.0 por debilidades sobre un elemento como podría ser un servicio web (apache, ngnix, …), que un servicio incrustado en un elemento o dispositivo de una red como podría ser un firewall, balanceador, etc (Big IP F5, Cisco ASA, etc) donde la complejidad de aplicación del parche puede aumentar o decrecer, por lo que existe relatividad según el escenario.

En las diferentes clasificaciones se puede identificar que la actuación de aplicación de parches no debería ser la misma y mientras que el uno de ellos puede ser relativamente sencillo con una simple modificación del fichero de configuración y recarga del servicio, el otro podría requerir una actualización propia del firmware del dispositivo o tareas mas manuales requeridas por un operador de comunicaciones.

Esto tambien es extrapolable a la correccion de errores de las aplicaciones
web, donde la corrección de un error programático o de un componente vulnerable de la aplicación (por ejemplo una librería con vulnerabilidad), puede llevar todo un proceso de análisis y despliegue del aplicativo.

Si bien esto no se puede evitar siempre, si pondremos encima de la mesa
medidas proactivas para evitarlo en medida de lo posible, llegando a un flujo
de parcheo normalizado y lo suficientemente robusto donde hablaremos sobre los mismos en el apartado de las fases.

# Inventario

Debemos cumplir como mínimo un inventario robusto de que es lo que se tiene y nunca descuidarlo, manteniéndolo siempre actualizado. Existen diversas tecnologías de por ejemplo “descubrimiento” de sistemas, servicios y dispositivos, como podría ser BMC ADDM u otras como Node Expoter para integrar en Prometheus, Sensu, etc, que si bien se pueden apostar por ellas, debemos revisarlas y mantener el inventario de forma centralizada y actualizada independientemente la aplicación para recoleccionar las métricas que usemos.

# Tecnologías y herramientas

Hoy en día existen determinadas tecnologías que pueden ayudarnos agilizando los tiempos y automatizar en medida de lo posible estos ciclos.

Esto no implica que haya que suprimir fases como code reviews o los pentesting más manuales o "artesanos" para valorar si todo está correctamente securizado, pero si queremos agilizar el proceso y enfocarnos en parchear lo antes posible, estos deberán tomar un papel más relevante al finalizar el ciclo (no durante el mismo). Aún así podremos usar herramientas del tipo Gerrit, Crisol (Atlassian), Collaborator (SmartBear), etc.

Para ello vamos a describir unas cuantas y en que nos pueden ayudar:

> IDE hacks

Podemos apoyarnos en pequeños hacks de gran valor como plugins y linters para análisis superficiales de código estático, detectando así errores de codificación, malas prácticas e inclusive vulnerabilidades en el código. Para ello utilizaremos herramientas del tipo como OWASP ASIDE, Synopsys SecureAssist, FindSecBugs, etc.

Podemos considerar estos hacks ágiles no llegando a ser herramientas dedicadas al análisis de código estático y dinámico, no degradando el tiempo de actuación ante la generación de un nuevo parche, ya que las especializadas pueden llegar a consumir muchísimo tiempo y es recomendable lanzarlas en horarios valle. Si bien es adecuado agregar herramientas especializadas en el análisis de código estático y dinámico, éstas deben estar enfocadas a ejecutar en el mismo punto que lo estaría un code review, al finalizar el ciclo de entrega del parche, ya que cubriremos unos mínimos mediante los hacks y podremos realizar la entrega del parche de forma más ágil sin llegar a la mediocridad por no haber cubierto esta parte.

Algunas de las herramientas recomendadas para el análisis de código podrían ser IBM AppScan, Codacy, OWASP ZAP, SonarQube, etc.

> Automatización

La magia de la automatización tiene muchísimas ventajas, entre ellas la rapidez de despliegue y corrección de 1-N sistemas, servicios o aplicativos que tengamos automatizados, donde además se evitará el posible fallo humano por cada iteración de la acción a parchear, generando un proceso muy fiable y rápido. Existen múltiples herramientas de automatización como Puppet, Chef, Salt, Ansible, Terraform, etc.

  • Ansible: Es una herramienta orientada a la automatización que se caracteriza básicamente por llevar la configuración de infraestructuras, sistemas, servicios y/o aplicaciones a código, concretamente en formato YAML, pudiendo escribir, mantener ese código y lanzarlo sobre la arquitectura que queramos para que se produzca la automagia :).
    Si bien requiere trabajo duro dejarlo bien definido, una vez hecho, el ahorro de tiempo será visible siendo lo que obtendremos como principal resultado.
    Es importante indicar que gracias a múltiples módulos de la herramienta que desarrolla la comunidad, tendremos la capacidad de automatizar configuraciones desde servicios básicos como Apache, Nginx, etc hasta sistemas operativos completos como Debian, Oracle Linux, etc. Ademas tenemos infinidad de módulos relativos a dispositivos de red como podrían ser para elementos como un Cisco ASA o un Big IP F5. Las posibilidades de automatización mediante módulos es realmente prometedora.

> Testing

La parte y práctica de testing suele ir enfocanda al desarrollo del software. Esto no implica que podamos integrar testing, no solo para el desarrollo de un producto, si no para la parte de patching e inclusive para la parte de orquestación de una infraestructura, pues recordemos que debemos llevar las tareas de infraestructura (sistemas y servicios) a código y mediante el testing, controlar que todo se está realizando de manera correcta.

Aquí meteremos una parte de seguridad clave para garantizar unos mínimos y evitar la labor de aprovación por parte de un responsable de seguridad verificando que se cumplen los requerimientos mínimos de seguridad en lo que se está haciendo. Para ello, estos requerimientos los transladaremos a tests, como por ejemplo la verificación de transferencias seguras, autenticación segura, que no existen vulnerabilidades de tipo XSS, SQLi, etc.

Esto nos permite generar calidad en lo que estamos haciendo y garantizar, en gran parte, que nuestras acciones están siendo securizadas sin un factor humano y manual, trasladado todo a tests. Ahora si… el número y calidad de los tests que integremos correrá de nuestra parte, por lo que aquí es donde habrá que hacer especial incapié en generar tests lo suficientemente robustos, de calidad y con idempotencia suficiente para garantizar unos mínimos.

La parte de los tests la podríamos divivir inicialmente en la verificación de tests unitarios y funcionales a nivel de seguridad. No vamos a profundizar ahora en los mismos, porque daría para varios posts, pero básicamente debemos entender que con los unitarios comprobaremos a nivel programático que el comportamiento es el esperado dándonos visibilidad para la posible refactorización del código, mientras que los funcionales abarcarían pruebas de regresión, exploratorias, compatibilidad, integración y aceptación.

Nos apoyaremos en herramientas como:

  • Jenkins: Es uns ervicio de integración continua encargada de ejecutar procesos o tareas programadas de construcción del código de manera automática siguiendo el pipeline que le indiquemos. Básicamente es la herramienta con la que nos apoyaremos durante el flujo e integraremos otras como por ejemplo Ansible, BDD Security, etc para llevar a cabo el pipeline.
  • BDD Security Framework: Durante las fases de desarrollo, podemos incluir este framework donde el paradigma del analisis de unos requerimientos de seguridad cambia de orden, es decir, cuando se realiza el ciclo de desarrollo y testeo y se va a producir el despliegue del codigo, existen una serie de test que podemos definir nosotros mediante este framework donde validarian si pasa una serie de requerimientos de seguridad.
    Como ya comentábamos, esto modifica que tenga que existir una validacion humana por parte de un auditor una vez termina el ciclo de desarrollo y se analice el mismo antes de realizar push de nuestro código. Se pueden incluir test de tipo XSS, SQLi, Fuzzing o lo que se nos ocurra para garantizar la seguridad.

También sería viable integrar OSS-Fuzz y herramientas del tipo Nessus/Qualys/OpenVAS, pero dado que pueden llegar a ralentizar y queremos agilidad, ya trataremos en otro post como afrontar esto, puesto que da para largo :).

> Monitorización

La parte de la monitorización es clave para tener una visibilidad real de lo que está pasando durante nuestros procesos o inclusive en la parte defensiva cuando se pueda estar recibiendo un ataque. Para ello nos apoyaremos en herramientas SIEM e IPS/IDS/HIDS.

Con la ayuda de herramientas de tipo IDS tendremos la capacidad de analizar la red en casi "tiempo real" utilizando motores basados en firmas, heurística, etc para la detección de amenazas, como por ejemplo Suricata.

Estas herramientas generan multiples logs que serán necesario revisar, identificar posibles falsos positivos y correlacionar los eventos sucedidos para tener una compresión clara de que está pasando. Para ello usaremos herramientas de tipo SIEM.

  • Suricata: Es un conocido IDS que nos permitiría tener esa “visibilidad” de lo que esta pasando. Básicamente va analizando la red en tiempo real en busca de amenazas utilizando firmas o reglas definidas para la detección de las mismas. Tener una herramienta de este tipo en la parte de monitorización es "básico", sobre todo para identificar posibles ataques de cara a averiguar la problemática y poner remediación con un parche concreto a ese problema, pues a veces tendremos que parchear nosotros ante vulnerabilidades desconocidas hasta que salga el parche oficial.
  • SELKS: Es la unión de un IDS como es Suricata con el stack ELK para la correlación de logs, creando lo que podríamos denominar un IDS + SIEM, dotándonos de todo lo necesario para analizar estas amenazas.
    Más adelante hablaremos en otro post sobre el stack SELKS (Suricata Elasticsearch Kibana Scirius + Evebox) y como montarlo en alta disponibilidad con reglas de la comunidad y personalizadas por nosotros. También existen otras herramientas que funcionan muy bien como podría ser Logtrust y Splunk con un soporte detrás.

# Roles

Debemos definir siempre una serie de responsables, intentando quitar lo
máximo posible el mayor numero de humanos responsables de la validación e
intentando llevar esta validacion de forma programática.

Si por cada pipeline o flujo completado de aplicación de parches se requiere
a un responsable para la validación, el cual no dispone de tiempo o hay que
seguir una burocracia densa para la validacion del parche… estamos perdidos.

Para ello se debe analizar y llevar a codigo todo lo que sea posible de
validar sin un requerimiento humano.

Nos apoyaremos en el testing comentado anteriormente, realizando los mismos con la propia colaboración de diversas áreas como podría ser QA, UIX, Producto, Explotación, etc.

Si la validación por parte de un área viene dada a verificar un comportamiento exacto de un aplicativo, ¿por qué darle más trabajo al área y ralentizar nuestro trabajo cuando puede hacerlo un “test”?

Esa es la idea, llevar todo lo viable a código, siempre tras realizar un análisis en profundidad.

Evidentemente habran verificaciones que de primeras no nos podamos quitar, pero la automatizacion va avanzando y a día de hoy existen muchas soluciones que mediante nuestra creatividad podemos solucionar muchos de estos problemas y como resultado quitarnos un porcentaje alto de absurdas comprobaciones y lentitud en la aplicación de las medidas de seguridad
o correctoras.

Como conclusion podríamos decir, que se debe definir como responsables
de la verificacion cada area hasta que se tenga el “test” validado por la misma
para ir quitando, poco a poco, esas verificaciones finales “humanas” y delegando en el responsable de seguridad la comodidad de saber que está funcionando bien la aplicación del parche.

Evidentemente, dentro del desarrollo de estos tests, el responsable de
seguridad debe validar que se aplican correctamente, con los tests suficientes
como para garantizar que la problematica de seguridad está corregida.

# Fases

Podríamos decir que debe tener mínimo 3 fases:

  • Identificación: Aquí debemos ir identificando para los sistemas, servicios y/o aplicaciones el nombre y versión.
  • Evaluación: Aquí definiremos las diferentes formas de evaluación del riesgo, determinando quienes deben realizar estas evaluaciones y agrupando las mismas.
  • Corrección: Definiremos el flujo exacto de la aplicación del parche,
    incluyendo los departamentos o areas responsables de cada proceso, donde debemos indicar los requisitos y procesos necesarios para poder aplicar las medidas correctoras según la tipología o clasificación del riesgo.

Para ello lo ideal seria separar los flujos dividiendolos en:

  • Securización del pipeline: Aquí debemos centrarnos en tareas realtivas al hardening del proceso de pipeline, normalmente subestimado. En el mismo deberemos hacer un tratamiento segun el elemento que queramos abordar (sistema, servicio o app) en tareas como podrian ser:
    — Autenticación requerida para los cambios
    — Traceo de logs de todo lo que se esta haciendo
    — Sistemas de manejo de claves
    — Almacenamiento seguro y aislado para la construcción y/o despliegue
    — …
  • Securización durante el pipeline: Aquí debemos implementar unos requerimientos que de seguridad que deban pasar y cumplir durante ese pipeline los sistemas, servicios y/o aplicaciones que vayamos a desplegar. Tareas relativas a esto podrian ser:
    — Testeo automatizado de seguridad en base a versiones (paquetería)
    — Análisis de código estatico
    — …
  • Automatización del parcheo: Por ultimo toda la fase de automatización debe ser securizada también, desde el manejo del código de las configuraciones a desplegar, automatización ante un incidente (disaster recovery, rollbacks, etc), securizacion de backups, monitorizacion de logs, etc.

> Identificación

En esta tarea haremos uso de ese maravilloso inventario que deberíamos tener y realizaremos consultas relativas a las versiones exactas que disponen de sea vulnerabilidad, localizando:

  • Nombre del producto
  • Versión del producto
  • Soporte del producto (Con actualizaciones de seguridad vía gestor de paquetes o sin soporte de actualizaciones de seguridad)

> Evaluación

Debemos evaluar los riesgos y para ello definiremos los responsables
de las áreas relativas a la toma de decisiones finales y/o validación de los
elementos que corregiremos (area de seguridad, explotación, producto, etc).

Podriamos clasificar como valorar el riesgo en dos bloques:

>> Vulnerabilidad

Se debe tomar como base para el riesgo por vulnerabilidad el conocido CVSS (Commom Vulnerability Scoring System) reportado en el informe o bien de herramientas que realizarían un análisis recurrente (por ejemplo diarío) de las vulnerabilidades. Estas herramientas podrían ser del tipo Nessus, Qualys o inclusive OpenVAS.

Esto implica que se debe tener una prioridad clara a la resolución de vulnerabilidades críticas que en sí a la propia cantidad, priorizando siempre la actuación para la corección de la vulnerabilidad en el sistema, servicio o aplicación en base al score obtenido que a la cantidad de vulnerabilidades reportadas con un score más bajo.

Mucha gente tiende a presentar un informe con multiples vulnerabilides donde las cuales ni si quiera se han analizado en profundidad para valorar el alcance real en nuestras plataformas, nuestra misión sería realizar este análisis.

Para un correcto análisis sería necesario disponer del equipo humano dedicado a la investación e implementación de pruebas de concepto de estas vulnerabilidades y/o debilidades en medida de lo posible, y una continua formación en diferentes disciplinas de las ramas de seguridad, ya que si realizamos un trabajo “mediocre” en esta parte, no tendremos una capacidad critica real de toma de decisión ante una nueva amenaza, pudiendo entrar en los flujos de parcheo de forma errática por una mala decisión.

>> Arquitectura

  • Disponibilidad: Se debe clasificar el tipo de arquitectura a la que nos enfrentamos con la aplicación del parche corrector, por lo que no será lo mismo una arquitectura standalone que distribuida, balanceada, en alta disponibilidad, etc. Para ello nos centraremos principalmente en la disponibilidad y determinaremos diferentes grados de la misma, donde podríamos asignar un peso y realizar una media en base a las siguientes elementos a valorar en una arquitectura (riesgo y requisitos). Por ejemplo disponibilidad alta, media, baja.
  • Riesgo: Aquí debemos asignar valores de riesgo que podrían ser crítico, alto, medio, bajo e informativo y a su vez declararlos con tiempos límite de corrección, quedando tal que así:
# Crítico:
- Tiempo límite de corrección: 24 horas
# Alto:
- Tiempo límite de corrección: 48 horas
# Medio:
- Tiempo límite de corrección: 72 horas
# Bajo:
- Tiempo límite de corrección: 7 días
# Informativo:
- Tiempo límite de corrección: 10 días
  • Requisitos: Se debe establecer una serie de requisitos mínimos para asegurar que en caso de que algo vaya mal, se pueda volver al estado previo. Esto podría solventarse con respaldos de los sistemas, servicios y/o aplicaciones antes de aplicar los parches, validando el estado previo, usar sistemas de preproducción, si existen tecnologías de virtualización generar instantáneas, si existe alta disponibilidad para el servicio aprovecharlo, etc.

> Corrección

Determinaremos que precisaremos antes de aplicar el parche, que se deben cumplir unos requisitos previos, que pueden ser comunes para todo o bien podriamos clasificar según el tipo de corrección hagamos, por lo que declararemos:

>> Tipo de corrección

  • Automatizada: Este tipo de corrección estaría asociado a aquellos sistemas, servicios y/o aplicaciones que dispongan de un sistema de paquetería asociada a las actualizaciones de seguridad, cubriendo las necesidades básicas para la corrección de la vulnerabilidad. Un ejemplo lo podríamos tener con sistemas debian y repositorios del tipo http://security.debian.org/debian-security. También habría que definir una periocidad con un tiempo máximo de actualización para evitar llegar al punto de que nos encontremos con el problema y se vaya acumulando en una cola que luego crecerá y aumentará el riesgo.
  • Manual: Es tipo de corrección estaría asociado a esos sistemas, servicios y/o aplicaciones que sea factible de corregir, ya sea por el contexto en el que están, las versiones de los componentes permiten que se puedan actualizar sin perdida de servicio, etc pero que no se cubra la corrección de parche mediante una actualización propia desde el sistema gestor de paquetes. Por ejemplo, podría darse el caso de tener que modificar un fichero de configuración personalizado para garantizar la seguridad del sistema, servicio y/o aplicación.

>> Requerimientos previos

Previamente debemos cumplir uns requerimientos para garantizar la estabilidad y disponibilidad de ese componente a aplicar el parche.

Unos requisitos que podríamos tomar como base y comunes para cualquier tipo de aplicación del parche (automatizada o manual), serían los siguientes:

  1. Cumplir los requisitos de arquitectura que hemos definido previamente.
  2. Asegurar la existencia de respaldos y tener la capacidad de realizar el rollback de una forma ágil.
  3. Utilizar los recursos disponibles para validar el correcto funcionamiento, como podrían ser entornos de preproducción, gestionar la alta disponibilidad en los entornos de producción, etc.
  4. Establecer un tiempo máximo de verificación del correcto funcionamiento para validar si realizar rollback o no. Por supuesto esto no quita que haya que probar previamente en los entornos de preproducción ya comentado en el punto anterior.

> Contingencia

Muchas empresas disponen de lo que se denomina legacy software, donde suelen ser un lastre por no decir, un tiro en el pie.

Mientras solucionamos este problema, lo único que podemos hacer para "parchear" estos sistemas, servicios y/o aplicativos, es… migrarlos o aislarlos.

Por lo que podemos definir que tendríamos dos formas de solucionar esto:

  • Migración: La misma podría suponer un esfuerzo bastante grande, por lo que lo que es adecuado comenzar aislando previamente para luego migrar.
  • Aislamiento: Básicamente esto es lo último que deberíamos hacer, pero si no queda más remedio tendremos que aislar ese sistema, servicio y/o aplicativo por medio de técnicas de bastionado, reglas de seguridad perimetral, acceso controlado hosts, firewalls de red y/o aplicación, detectores de intrusiones, private vlans, etc.

--

--

JS

He who fights with monsters should be careful lest he thereby become a monster. And if thou gaze long into an abyss, the abyss will also gaze into thee.